diff --git a/autoload/extractor_240.rb b/autoload/extractor_240.rb new file mode 100644 index 0000000..a6b46f4 --- /dev/null +++ b/autoload/extractor_240.rb @@ -0,0 +1,185 @@ +require File.join(File.dirname(__FILE__), '..', 'vendor', 'patm', 'lib', 'patm.rb') + +module RubyHlLvar + class Extractor + extend ::Patm::DSL + + def initialize(show_warning = false) + @show_warning = show_warning + end + + def warn(message) + ::Vim.message "[ruby_hl_lvar.vim] WARN: #{message}" if @show_warning + end + + # source:String -> [ [lvar_name:String, line:Numeric, col:Numeric]... ] + def extract(source) + sexp = Ripper.sexp(source) + extract_from_sexp(sexp) + end + + define_matcher :extract_from_sexp do|r| + p = Patm + __ = p._any + _xs = p._xs + + l_1 = p._1 + l_2 = p._2 + l_3 = p._3 + l_4 = p._4 + l_5 = p._5 + l_6 = p._6 + + # single assignment + r.on [:assign, [:var_field, [:@ident, l_1, [l_2, l_3]]], l_4] do|m, _self| + [[m._1, m._2, m._3]] + _self.extract_from_sexp(m._4) + end + # mass assignment + r.on [:massign, l_1, l_2] do|m, _self| + _self.handle_massign_lhs(m._1) + _self.extract_from_sexp(m._2) + end + # += + r.on [:opassign, [:var_field, [:@ident, l_1, [l_2, l_3]]], __, l_4] do|m, _self| + [[m._1, m._2, m._3]] + _self.extract_from_sexp(m._4) + end + # local variable reference + r.on [:var_ref, [:@ident, l_1, [l_2, l_3]]] do|m| + [[m._1, m._2, m._3]] + end + # rescue + r.on [:rescue, l_1, [:var_field, [:@ident, l_2, [l_3, l_4]]], l_5, l_6] do|m, _self| + [[m._2, m._3, m._4]] + _self.extract_from_sexp(m._1) + _self.extract_from_sexp(m._5) + _self.extract_from_sexp(m._6) + end + # method params + r.on [:params, _xs&l_1] do|m, _self| + _self.handle_normal_params(m._1[0]) + + _self.handle_default_params(m._1[1]) + + _self.handle_rest_param(m._1[2]) + + _self.handle_normal_params(m._1[3]) + + _self.handle_block_param(m._1[6]) + end + # for + r.on [:for, l_1, l_2, l_3] do|m, _self| + _self.handle_for_param(m._1) + _self.extract_from_sexp(m._2) + _self.extract_from_sexp(m._3) + end + r.on p.or(nil, true, false, Numeric, String, Symbol, []) do|m| + [] + end + r.else do|sexp, _self| + if sexp.is_a?(Array) && sexp.size > 0 + if sexp[0].is_a?(Symbol) # some struct + sexp[1..-1].flat_map {|elm| _self.extract_from_sexp(elm) } + else + sexp.flat_map{|elm| _self.extract_from_sexp(elm) } + end + else + _self.warn "Unsupported AST data: #{sexp.inspect}" + [] + end + end + end + + define_matcher :handle_massign_lhs_item do|r| + p = Patm + r.on [:@ident, p._1, [p._2, p._3]] do|m| + [[m._1, m._2, m._3]] + end + r.on [:mlhs_paren, p._1] do|m, _self| + _self.handle_massign_lhs(m._1) + end + r.on [:mlhs_add_star, p._1, p._2] do|m, _self| + _self.handle_massign_lhs(m._1) + _self.handle_massign_lhs([m._2]) + end + r.on [:mlhs_add_star, p._1, p._2, p._3] do|m, _self| + _self.handle_massign_lhs(m._1) + _self.handle_massign_lhs([m._2]) + _self.handle_massign_lhs(m._3) + end + r.on [:aref_field, p._1, p._2] do |m, _self| + _self.extract_from_sexp(m._1) + _self.extract_from_sexp(m._2) + end + r.on [p.or(:field, :@ivar, :@cvar, :@gvar, :@const), p._xs] do + [] + end + r.on nil do + [] + end + r.else do|obj, _self| + _self.warn "Unsupported ast item in handle_massign_lhs: #{obj.inspect}" + [] + end + end + + def handle_massign_lhs(lhs) + return [] unless lhs + if lhs.size > 0 && lhs[0].is_a?(Symbol) + lhs = [lhs] + end + lhs.flat_map {|expr| handle_massign_lhs_item(expr) } + end + + def handle_normal_params(list) + handle_massign_lhs(list) + end + + define_matcher :handle_rest_param do|r, _self| + p = Patm + r.on [:rest_param, [:@ident, p._1, [p._2, p._3]]] do|m| + [[m._1, m._2, m._3]] + end + r.on nil do + [] + end + r.on 0 do + [] + end + r.on [:rest_param, nil] do + [] + end + r.else do|obj, _self| + _self.warn "Unsupported ast item in handle_rest_params: #{obj.inspect}" + [] + end + end + + define_matcher :handle_block_param do|r| + p = ::Patm + r.on [:blockarg, [:@ident, p._1, [p._2, p._3]]] do|m| + [[m._1, m._2, m._3]] + end + r.on nil do + [] + end + r.else do|obj, _self| + _self.warn "Unsupported ast item in handle_block_params: #{obj.inspect}" + [] + end + end + + def handle_default_params(list) + return [] unless list + list.flat_map {|expr| handle_default_param(expr) } + end + + define_matcher :handle_default_param do|r| + p = Patm + r.on [[:@ident, p._1, [p._2, p._3]], p._any] do|m| + [[m._1, m._2, m._3]] + end + r.else do + [] + end + end + + define_matcher :handle_for_param do|r| + p = Patm + r.on [:var_field, [:@ident, p._1, [p._2, p._3]]] do|m, _self| + [[m._1, m._2, m._3]] + end + r.on [:var_field, p._xs] do|m, _self| + [] + end + r.else do|sexp, _self| + _self.handle_massign_lhs(sexp) + end + end + end +end diff --git a/autoload/extractor_250.rb b/autoload/extractor_250.rb new file mode 100644 index 0000000..62bde72 --- /dev/null +++ b/autoload/extractor_250.rb @@ -0,0 +1,185 @@ +require File.join(File.dirname(__FILE__), '..', 'vendor', 'patm', 'lib', 'patm.rb') + +module RubyHlLvar + class Extractor + extend ::Patm::DSL + + def initialize(show_warning = false) + @show_warning = show_warning + end + + def warn(message) + ::Vim.message "[ruby_hl_lvar.vim] WARN: #{message}" if @show_warning + end + + # source:String -> [ [lvar_name:String, line:Numeric, col:Numeric]... ] + def extract(source) + sexp = Ripper.sexp(source) + extract_from_sexp(sexp) + end + + define_matcher :extract_from_sexp do|r| + p = Patm + __ = p._any + _xs = p._xs + + l_1 = p._1 + l_2 = p._2 + l_3 = p._3 + l_4 = p._4 + l_5 = p._5 + l_6 = p._6 + + # single assignment + r.on [:assign, [:var_field, [:@ident, l_1, [l_2, l_3]]], l_4] do|m, _self| + [[m._1, m._2, m._3]] + _self.extract_from_sexp(m._4) + end + # mass assignment + r.on [:massign, l_1, l_2] do|m, _self| + _self.handle_massign_lhs(m._1) + _self.extract_from_sexp(m._2) + end + # += + r.on [:opassign, [:var_field, [:@ident, l_1, [l_2, l_3]]], __, l_4] do|m, _self| + [[m._1, m._2, m._3]] + _self.extract_from_sexp(m._4) + end + # local variable reference + r.on [:var_ref, [:@ident, l_1, [l_2, l_3]]] do|m| + [[m._1, m._2, m._3]] + end + # rescue + r.on [:rescue, l_1, [:var_field, [:@ident, l_2, [l_3, l_4]]], l_5, l_6] do|m, _self| + [[m._2, m._3, m._4]] + _self.extract_from_sexp(m._1) + _self.extract_from_sexp(m._5) + _self.extract_from_sexp(m._6) + end + # method params + r.on [:params, _xs&l_1] do|m, _self| + _self.handle_normal_params(m._1[0]) + + _self.handle_default_params(m._1[1]) + + _self.handle_rest_param(m._1[2]) + + _self.handle_normal_params(m._1[3]) + + _self.handle_block_param(m._1[6]) + end + # for + r.on [:for, l_1, l_2, l_3] do|m, _self| + _self.handle_for_param(m._1) + _self.extract_from_sexp(m._2) + _self.extract_from_sexp(m._3) + end + r.on p.or(nil, true, false, Numeric, String, Symbol, []) do|m| + [] + end + r.else do|sexp, _self| + if sexp.is_a?(Array) && sexp.size > 0 + if sexp[0].is_a?(Symbol) # some struct + sexp[1..-1].flat_map {|elm| _self.extract_from_sexp(elm) } + else + sexp.flat_map{|elm| _self.extract_from_sexp(elm) } + end + else + _self.warn "Unsupported AST data: #{sexp.inspect}" + [] + end + end + end + + define_matcher :handle_massign_lhs_item do|r| + p = Patm + r.on [:var_field, [:@ident, p._1, [p._2, p._3]]] do|m| + [[m._1, m._2, m._3]] + end + r.on [:@ident, p._1, [p._2, p._3]] do|m| + [[m._1, m._2, m._3]] + end + r.on [:mlhs, p._xs[:xs]] do|m, _self| + m[:xs].inject([]) {|lhss, l| lhss + _self.handle_massign_lhs(l) } + end + r.on [:aref_field, p._1, p._2] do |m, _self| + _self.extract_from_sexp(m._1) + _self.extract_from_sexp(m._2) + end + r.on [p.or(:field, :@ivar, :@cvar, :@gvar, :@const), p._xs] do + [] + end + r.on [:rest_param, p._1] do|m, _self| + _self.handle_massign_lhs_item(m._1) + end + r.on nil do + [] + end + r.else do|obj, _self| + _self.warn "Unsupported ast item in handle_massign_lhs: #{obj.inspect}" + [] + end + end + + def handle_massign_lhs(lhs) + return [] unless lhs + if lhs.size > 0 && lhs[0].is_a?(Symbol) + lhs = [lhs] + end + lhs.flat_map {|expr| handle_massign_lhs_item(expr) } + end + + def handle_normal_params(list) + handle_massign_lhs(list) + end + + define_matcher :handle_rest_param do|r, _self| + p = Patm + r.on [:rest_param, [:@ident, p._1, [p._2, p._3]]] do|m| + [[m._1, m._2, m._3]] + end + r.on nil do + [] + end + r.on 0 do + [] + end + r.on [:rest_param, nil] do + [] + end + r.else do|obj, _self| + _self.warn "Unsupported ast item in handle_rest_params: #{obj.inspect}" + [] + end + end + + define_matcher :handle_block_param do|r| + p = ::Patm + r.on [:blockarg, [:@ident, p._1, [p._2, p._3]]] do|m| + [[m._1, m._2, m._3]] + end + r.on nil do + [] + end + r.else do|obj, _self| + _self.warn "Unsupported ast item in handle_block_params: #{obj.inspect}" + [] + end + end + + def handle_default_params(list) + return [] unless list + list.flat_map {|expr| handle_default_param(expr) } + end + + define_matcher :handle_default_param do|r| + p = Patm + r.on [[:@ident, p._1, [p._2, p._3]], p._any] do|m| + [[m._1, m._2, m._3]] + end + r.else do + [] + end + end + + define_matcher :handle_for_param do|r| + p = Patm + r.on [:var_field, [:@ident, p._1, [p._2, p._3]]] do|m, _self| + [[m._1, m._2, m._3]] + end + r.on [:var_field, p._xs] do|m, _self| + [] + end + r.else do|sexp, _self| + _self.handle_massign_lhs(sexp) + end + end + end +end diff --git a/autoload/ruby_hl_lvar.vim.rb b/autoload/ruby_hl_lvar.vim.rb index c3d9b8c..40a8d0a 100644 --- a/autoload/ruby_hl_lvar.vim.rb +++ b/autoload/ruby_hl_lvar.vim.rb @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- require 'ripper' -require File.join(File.dirname(__FILE__), '..', 'vendor', 'patm', 'lib', 'patm.rb') def Vim.message(msg) # ::Vim.message and ::Kernel.puts seems weird behavior @@ -35,186 +34,8 @@ def self.with_error_handling end end -module RubyHlLvar - class Extractor - extend ::Patm::DSL - - def initialize(show_warning = false) - @show_warning = show_warning - end - - def warn(message) - ::Vim.message "[ruby_hl_lvar.vim] WARN: #{message}" if @show_warning - end - - # source:String -> [ [lvar_name:String, line:Numeric, col:Numeric]... ] - def extract(source) - sexp = Ripper.sexp(source) - extract_from_sexp(sexp) - end - - define_matcher :extract_from_sexp do|r| - p = Patm - __ = p._any - _xs = p._xs - - l_1 = p._1 - l_2 = p._2 - l_3 = p._3 - l_4 = p._4 - l_5 = p._5 - l_6 = p._6 - - # single assignment - r.on [:assign, [:var_field, [:@ident, l_1, [l_2, l_3]]], l_4] do|m, _self| - [[m._1, m._2, m._3]] + _self.extract_from_sexp(m._4) - end - # mass assignment - r.on [:massign, l_1, l_2] do|m, _self| - _self.handle_massign_lhs(m._1) + _self.extract_from_sexp(m._2) - end - # += - r.on [:opassign, [:var_field, [:@ident, l_1, [l_2, l_3]]], __, l_4] do|m, _self| - [[m._1, m._2, m._3]] + _self.extract_from_sexp(m._4) - end - # local variable reference - r.on [:var_ref, [:@ident, l_1, [l_2, l_3]]] do|m| - [[m._1, m._2, m._3]] - end - # rescue - r.on [:rescue, l_1, [:var_field, [:@ident, l_2, [l_3, l_4]]], l_5, l_6] do|m, _self| - [[m._2, m._3, m._4]] + _self.extract_from_sexp(m._1) + _self.extract_from_sexp(m._5) + _self.extract_from_sexp(m._6) - end - # method params - r.on [:params, _xs&l_1] do|m, _self| - _self.handle_normal_params(m._1[0]) + - _self.handle_default_params(m._1[1]) + - _self.handle_rest_param(m._1[2]) + - _self.handle_normal_params(m._1[3]) + - _self.handle_block_param(m._1[6]) - end - # for - r.on [:for, l_1, l_2, l_3] do|m, _self| - _self.handle_for_param(m._1) + _self.extract_from_sexp(m._2) + _self.extract_from_sexp(m._3) - end - r.on p.or(nil, true, false, Numeric, String, Symbol, []) do|m| - [] - end - r.else do|sexp, _self| - if sexp.is_a?(Array) && sexp.size > 0 - if sexp[0].is_a?(Symbol) # some struct - sexp[1..-1].flat_map {|elm| _self.extract_from_sexp(elm) } - else - sexp.flat_map{|elm| _self.extract_from_sexp(elm) } - end - else - _self.warn "Unsupported AST data: #{sexp.inspect}" - [] - end - end - end - - define_matcher :handle_massign_lhs_item do|r| - p = Patm - r.on [:@ident, p._1, [p._2, p._3]] do|m| - [[m._1, m._2, m._3]] - end - r.on [:mlhs_paren, p._1] do|m, _self| - _self.handle_massign_lhs(m._1) - end - r.on [:mlhs_add_star, p._1, p._2] do|m, _self| - _self.handle_massign_lhs(m._1) + _self.handle_massign_lhs([m._2]) - end - r.on [:mlhs_add_star, p._1, p._2, p._3] do|m, _self| - _self.handle_massign_lhs(m._1) + _self.handle_massign_lhs([m._2]) + _self.handle_massign_lhs(m._3) - end - r.on [:aref_field, p._1, p._2] do |m, _self| - _self.extract_from_sexp(m._1) + _self.extract_from_sexp(m._2) - end - r.on [p.or(:field, :@ivar, :@cvar, :@gvar, :@const), p._xs] do - [] - end - r.on nil do - [] - end - r.else do|obj, _self| - _self.warn "Unsupported ast item in handle_massign_lhs: #{obj.inspect}" - [] - end - end - - def handle_massign_lhs(lhs) - return [] unless lhs - if lhs.size > 0 && lhs[0].is_a?(Symbol) - lhs = [lhs] - end - lhs.flat_map {|expr| handle_massign_lhs_item(expr) } - end - - def handle_normal_params(list) - handle_massign_lhs(list) - end - - define_matcher :handle_rest_param do|r, _self| - p = Patm - r.on [:rest_param, [:@ident, p._1, [p._2, p._3]]] do|m| - [[m._1, m._2, m._3]] - end - r.on nil do - [] - end - r.on 0 do - [] - end - r.on [:rest_param, nil] do - [] - end - r.else do|obj, _self| - _self.warn "Unsupported ast item in handle_rest_params: #{obj.inspect}" - [] - end - end - - define_matcher :handle_block_param do|r| - p = ::Patm - r.on [:blockarg, [:@ident, p._1, [p._2, p._3]]] do|m| - [[m._1, m._2, m._3]] - end - r.on nil do - [] - end - r.else do|obj, _self| - _self.warn "Unsupported ast item in handle_block_params: #{obj.inspect}" - [] - end - end - - def handle_default_params(list) - return [] unless list - list.flat_map {|expr| handle_default_param(expr) } - end - - define_matcher :handle_default_param do|r| - p = Patm - r.on [[:@ident, p._1, [p._2, p._3]], p._any] do|m| - [[m._1, m._2, m._3]] - end - r.else do - [] - end - end - - define_matcher :handle_for_param do|r| - p = Patm - r.on [:var_field, [:@ident, p._1, [p._2, p._3]]] do|m, _self| - [[m._1, m._2, m._3]] - end - r.on [:var_field, p._xs] do|m, _self| - [] - end - r.else do|sexp, _self| - _self.handle_massign_lhs(sexp) - end - end - end +if Gem::Version.create(RUBY_VERSION) < Gem::Version.create('2.5.0') + require File.join(File.dirname(__FILE__), 'extractor_240.rb') +else + require File.join(File.dirname(__FILE__), 'extractor_250.rb') end diff --git a/vendor/patm/.gitignore b/vendor/patm/.gitignore index 7ae6fcf..6af2ca3 100644 --- a/vendor/patm/.gitignore +++ b/vendor/patm/.gitignore @@ -1,2 +1,4 @@ Gemfile.lock *.gem +coverage +coverage.vim diff --git a/vendor/patm/.travis.yml b/vendor/patm/.travis.yml new file mode 100644 index 0000000..048b871 --- /dev/null +++ b/vendor/patm/.travis.yml @@ -0,0 +1,7 @@ +language: ruby +rvm: + - 2.1.2 + - 2.0.0 + - 1.9.3 + - 1.9.2 +script: rspec diff --git a/vendor/patm/README.md b/vendor/patm/README.md index b25b8ad..75eb6b4 100644 --- a/vendor/patm/README.md +++ b/vendor/patm/README.md @@ -1,11 +1,45 @@ # PATM: PATtern Matcher for Ruby +[![Build Status](https://travis-ci.org/todesking/patm.svg?branch=master)](https://travis-ci.org/todesking/patm) +[![Code Climate](https://codeclimate.com/github/todesking/patm.png)](https://codeclimate.com/github/todesking/patm) + +PATM is extremely faster pattern match library. + +Features: Match value/classes, Capture, Array/Hash decomposition. + ## Usage ```ruby require 'patm' ``` +```ruby +# With DSL +class A + extend ::Patm::DSL + + define_matcher :match1 do|r| + p = Patm + r.on [:x, p._1, p._2] do|m| + [m._1, m._2] + end + end + + define_matcher :match2 do|r| + r.on [:a, Patm._xs & Patm._1] do|m, _self| + _self.match1(m._1) + end + # ... + r.else do + nil + end + end +end + +A.new.match1([:x, 1, 2]) +# => [1, 2] +``` + ```ruby # With case(simple but slow) def match(obj) @@ -52,57 +86,147 @@ rule.apply([]) # => nil ``` +## DSL + ```ruby -# With cached rules -class A - def initialize - @rules = Patm::RuleCache.new +class PatternMatcher + extend Patm::DSL + + define_matcher(:match) do|r| # r is instance of Patm::Rule + # r.on( PATTERN ) {|match, _self| + # First argument is instance of Patm::Match. Use it to access captured value. + # ex. m._1, m._2, ..., m[capture_name] + # + # Second argument is instance of the class. Use it to access other methods. + # ex. _self.other_method + # } + # + # r.else {|value, _self| + # First argument is the value. Second is instance of the class. + # } end +end - def match1(obj) - @rules.match(:match1, obj) do|r| - p = Patm - r.on [:x, p._1, p._2] do|m| - [m._1, m._2] - end - end - end +matcher = PatternMatcher.new - def match2(obj) - @rules.match(:match2, obj) do|r| - # ... - end - end -end - ``` +matcher.match(1) +``` -```ruby -# With DSL -class A - extend ::Patm::DSL +## Patterns - define_matcher :match1 do|r| - p = Patm - r.on [:x, p._1, p._2] do|m| - [m._1, m._2] - end - end +### Value - define_matcher :match2 do|r| - r.on [:a, Patm._xs & Patm._1] do|m, _self| - _self.match1(m._1) - end - # ... - end +Value patterns such as `1, :x, String, ...` matches if `pattern === target_value` is true. + +### Struct + +```ruby +A = Struct.new(:x, :y) + +# ... +define_matcher :match_struct do|r| + # use Patm[struct_class].( ... ) for match struct + # argument is a Hash(member_name => pattern) or patterns that correspond struct members. + r.on Patm[A].(x: 1, y: Patm._1) {|m| m._1 } + r.on Patm[A].(2, Patm._1) {|m| m._1 } end +``` + +### Array + +`[1, 2, _any]` matches `[1, 2, 3]`, `[1, 2, :x]`, etc. + +`[1, 2, _xs]` matches `[1, 2]`, `[1, 2, 3]`, `[1, 2, 3, 4]`, etc. + +`[1, _xs, 2]` matches `[1, 2]`, `[1, 10, 2]`, etc. + +Note: More than one `_xs` in same array is invalid. + +### Hash + +`{a: 1}` matches `{a: 1}`, `{a: 1, b: 2}`, etc. + +`{a: 1, Patm.exact => true}` matches only `{a: 1}`. + +`{a: 1, b: Patm.opt(2)}` matches `{a: 1}`, `{a: 1, b: 2}`. + +### Capture + +`_1`, `_2`, etc matches any value, and capture the value as correspond match group. + +`Pattern#[capture_name]` also used for capture.`Patm._any[:foo]` capture any value as `foo`. + +Captured values are accessible through `Match#_1, _2, ...` and `Match#[capture_name]` + +### Compose + +`_1&[_any, _any]` matches any two element array, and capture the array as _1. +`Patm.or(1, 2)` matches 1 or 2. + + +## Performance + +see [benchmark code](./benchmark/comparison.rb) for details + +Machine: MacBook Air(Late 2010) C2D 1.8GHz, OS X 10.9.2 + +``` +RUBY_VERSION: 2.1.2 p95 + +Benchmark: Empty(x10000) + user system total real +manual 0.010000 0.000000 0.010000 ( 0.012252) +patm 0.060000 0.000000 0.060000 ( 0.057050) +pattern_match 1.710000 0.010000 1.720000 ( 1.765749) + +Benchmark: SimpleConst(x10000) + user system total real +manual 0.020000 0.000000 0.020000 ( 0.018274) +patm 0.060000 0.000000 0.060000 ( 0.075068) +patm_case 0.160000 0.000000 0.160000 ( 0.161002) +pattern_match 1.960000 0.020000 1.980000 ( 2.007936) + +Benchmark: ArrayDecomposition(x10000) + user system total real +manual 0.050000 0.000000 0.050000 ( 0.047948) +patm 0.250000 0.000000 0.250000 ( 0.254039) +patm_case 1.710000 0.000000 1.710000 ( 1.765656) +pattern_match 12.890000 0.060000 12.950000 ( 13.343334) + +Benchmark: VarArray(x10000) + user system total real +manual 0.050000 0.000000 0.050000 ( 0.052425) +patm 0.210000 0.000000 0.210000 ( 0.223190) +patm_case 1.440000 0.000000 1.440000 ( 1.587535) +pattern_match 10.050000 0.070000 10.120000 ( 10.898683) +``` -A.new.match1([:x, 1, 2]) -# => [1, 2] - ``` ## Changes -### x.x.x +### 3.1.0 + +- Struct matcher + +### 3.0.0 + +- If given value can't match any pattern and no `else`, `Patm::NoMatchError` raised(Instead of return nil). +- RuleCache is now obsoleted. Use DSL. +- More optimized compiler + +### 2.0.1 + +- Bugfix: About pattern `Patm._1 & Array`. +- Bugfix: Compiler bug fix. + +### 2.0.0 + +- Named capture +- Patm::GROUPS is obsolete. Use `pattern[number_or_name]` or `Patm._1, _2, ...` instead. +- More optimize for compiled pattern. +- Hash patterns + +### 1.0.0 - DSL - Compile is enabled by default diff --git a/vendor/patm/benchmark/benchmark.rb b/vendor/patm/benchmark/benchmark.rb index d0012d1..585e287 100644 --- a/vendor/patm/benchmark/benchmark.rb +++ b/vendor/patm/benchmark/benchmark.rb @@ -53,8 +53,8 @@ def match_with_case(obj) end end -@rule = P::Rule.new(false, &@ruledef) -@compiled_rule = P::Rule.new(true, &@ruledef) +@rule = P::Rule.new(&@ruledef) +@compiled_rule = P::Rule.new(&@ruledef).compile def match_with_rule(obj) @rule.apply(obj) diff --git a/vendor/patm/benchmark/comparison.rb b/vendor/patm/benchmark/comparison.rb new file mode 100644 index 0000000..e318857 --- /dev/null +++ b/vendor/patm/benchmark/comparison.rb @@ -0,0 +1,209 @@ +require 'benchmark' + +def benchmark(klass, n) + puts "Benchmark: #{klass}(x#{n})" + + target_methods = klass.instance_methods - Object.instance_methods - [:test_values] + validate(klass, target_methods) + + Benchmark.bm('pattern-match'.size) do|b| + obj = klass.new + test_values = obj.test_values + target_methods.each do|method_name| + b.report(method_name) do + m = obj.method(method_name) + n.times { test_values.each {|val| m.call(val) } } + end + end + end + puts +end + +def validate(klass, target_methods) + obj = klass.new + obj.test_values.each do|val| + results = target_methods.map {|name| [name, obj.public_send(name, val)] } + unless results.each_cons(2).all?{|(_, a), (_, b)| a == b } + raise "ERROR: Result not match. val=#{val.inspect}, #{results.map{|n,r| "#{n}=#{r.inspect}"}.join(', ')}" + end + end +end + +load File.join(File.dirname(__FILE__), '../lib/patm.rb') +require 'pattern-match' + +class Empty + extend Patm::DSL + + def manual(obj) + nil + end + + define_matcher :patm do|r| + r.else { nil } + end + + def pattern_match(obj) + match(obj) do + with(_) { nil } + end + end + + def test_values + [1, 2, 3] + end +end + +class SimpleConst + extend Patm::DSL + + def manual(obj) + if obj == 1 + 100 + elsif obj == 2 + 200 + else + 300 + end + end + + define_matcher :patm do|r| + r.on(1) { 100 } + r.on(2) { 200 } + r.else { 300 } + end + + def patm_case(obj) + case obj + when m = Patm.match(1) + 100 + when m = Patm.match(2) + 200 + else + 300 + end + end + + def pattern_match(obj) + match(obj) do + with(1) { 100 } + with(2) { 200 } + with(_) { 300 } + end + end + + def test_values + [1, 2, 3] + end +end + +class ArrayDecomposition + extend Patm::DSL + + def manual(obj) + return 100 unless obj + return nil unless obj.is_a?(Array) + return nil if obj.size != 3 + return nil unless obj[0] == 1 + + if obj[2] == 2 + obj[1] + else + [obj[1], obj[2]] + end + end + + define_matcher :patm do|r| + _1, _2 = Patm._1, Patm._2 + r.on([1, _1, 2]) {|m| m._1 } + r.on([1, _1, _2]) {|m| [m._1, m._2] } + r.on(nil) { 100 } + r.else { nil } + end + + def patm_case(obj) + _1, _2 = Patm._1, Patm._2 + case obj + when m = Patm.match([1, _1, 2]) + m._1 + when m = Patm.match([1, _1, _2]) + [m._1, m._2] + when m = Patm.match(nil) + 100 + else + nil + end + end + + def pattern_match(obj) + match(obj) do + with(_[1, _1, 2]) { _1 } + with(_[1, _1, _2]) { [_1, _2] } + with(nil) { 100 } + with(_) { nil } + end + end + + def test_values + [ + [], + [1, 9, 2], + [1, 9, 3], + [1, 9, 1], + [1], + "foo", + nil + ] + end +end + +class VarArray + extend Patm::DSL + + def manual(obj) + return nil unless obj.is_a?(Array) && obj.size >= 2 && obj[0] == 1 && obj[1] == 2 + return obj[2..-1] + end + + define_matcher :patm do|r| + r.on([1, 2, Patm._xs[1]]) {|m| m[1] } + r.else { nil } + end + + def patm_case(obj) + case obj + when m = Patm.match([1, 2, Patm._xs[1]]) + m._1 + else + nil + end + end + + def pattern_match(obj) + match(obj) do + with(_[1, 2, *_1]) { _1 } + with(_) { nil } + end + end + + def test_values + [ + nil, + 100, + [], + [1, 2], + [1, 2, 3], + [1, 2, 3, 4], + [1, 10, 100], + ] + end +end + + +puts "RUBY_VERSION: #{RUBY_VERSION} p#{RUBY_PATCHLEVEL}" +puts + +benchmark Empty, 10000 +benchmark SimpleConst, 10000 +benchmark ArrayDecomposition, 10000 +benchmark VarArray, 10000 diff --git a/vendor/patm/lib/patm.rb b/vendor/patm/lib/patm.rb index 30c0e86..051a78c 100644 --- a/vendor/patm/lib/patm.rb +++ b/vendor/patm/lib/patm.rb @@ -1,44 +1,97 @@ module Patm + class NoMatchError < StandardError + def initialize(value) + super("Pattern not match: value=#{value.inspect}") + @value = value + end + attr_reader :value + end class Pattern def self.build_from(plain) case plain when Pattern plain - when Array - array = plain.map{|a| build_from(a)} - rest_index = array.index(&:rest?) - if rest_index - head = array[0...rest_index] - rest = array[rest_index] - tail = array[(rest_index+1)..-1] - Arr.new(head, rest, tail) - else - Arr.new(array) - end + when ::Array + build_from_array(plain) + when ::Hash + build_from_hash(plain) else Obj.new(plain) end end + def self.build_from_array(plain) + array = plain.map{|a| build_from(a)} + rest_index = array.index(&:rest?) + if rest_index + head = array[0...rest_index] + rest = array[rest_index] + tail = array[(rest_index+1)..-1] + Arr.new(head, rest, tail) + else + Arr.new(array) + end + end + + def self.build_from_hash(plain) + self::Hash.new( + plain.each_with_object({}) do|(k, v), h| + h[k] = build_from(v) if k != Patm.exact + end, + plain[Patm.exact] + ) + end + + module Util + def self.compile_value(value, free_index) + if value.nil? || value.is_a?(Numeric) || value.is_a?(String) || value.is_a?(Symbol) + [ + value.inspect, + [], + free_index, + ] + else + [ + "_ctx[#{free_index}]", + [value], + free_index + 1, + ] + end + end + end + + # Use in Hash pattern. + def opt + Opt.new(self) + end + def execute(match, obj); true; end + def opt? + false + end + def rest? false end def &(rhs) - And.new([self, rhs]) + And.new([self, Pattern.build_from(rhs)]) + end + + def [](name) + self & Named.new(name) end def compile src, context, _ = self.compile_internal(0) - Compiled.new(self.inspect, src, context) + Compiled.new(self.inspect, src || "true", context) end - # free_index:Numeric -> [src, context, free_index] - # variables: _ctx, _match, _obj + # free_index:Numeric -> target_name:String -> [src:String|nil, context:Array, free_index:Numeric] + # variables: _ctx, _match, (target_name) def compile_internal(free_index, target_name = "_obj") [ "_ctx[#{free_index}].execute(_match, #{target_name})", @@ -52,6 +105,7 @@ def initialize(desc, src, context) @desc = desc @context = context singleton_class = class <#{@desc}"; end end + class Hash < self + def initialize(hash, exact) + @pat = hash + @exact = exact + @non_opt_count = @pat.values.count{|v| !v.opt? } + end + def execute(match, obj) + return false unless obj.is_a?(::Hash) + obj.size >= @non_opt_count && + (!@exact || obj.keys.all?{|k| @pat.has_key?(k) }) && + @pat.all? {|k, pat| + if obj.has_key?(k) + pat.execute(match, obj[k]) + else + pat.opt? + end + } + end + def inspect; @pat.inspect; end + def compile_internal(free_index, target_name = "_obj") + i = free_index + ctxs = [] + src = [] + + ctxs << [@pat] + i += 1 + + pat = "_ctx[#{free_index}]" + src << "#{target_name}.is_a?(::Hash)" + src << "#{target_name}.size >= #{@non_opt_count}" + if @exact + src << "#{target_name}.keys.all?{|k| #{pat}.has_key?(k) }" + end + tname = "#{target_name}_elm" + @pat.each do|k, v| + key_src, c, i = Util.compile_value(k, i) + ctxs << c + s, c, i = v.compile_internal(i, tname) + body = + if s + "(#{tname} = #{target_name}[#{key_src}]; #{s})" + else + "true" + end + src << + if v.opt? + "(!#{target_name}.has_key?(#{key_src}) || #{body})" + else + "(#{target_name}.has_key?(#{key_src}) && #{body})" + end + ctxs << c + end + [ + src.join(" &&\n"), + ctxs.flatten(1), + i, + ] + end + end + + class Opt < self + def initialize(pat) + @pat = pat + end + def opt? + true + end + def execute(match, obj) + @pat.execute(match, obj) + end + def inspect; "?#{@pat.inspect}"; end + def compile_internal(free_index, target_name = "_obj") + @pat.compile_internal(free_index, target_name) + end + end + class Arr < self def initialize(head, rest = nil, tail = []) @head = head @@ -77,16 +208,24 @@ def initialize(head, rest = nil, tail = []) end def execute(mmatch, obj) - size_min = @head.size + @tail.size return false unless obj.is_a?(Array) - return false unless @rest ? (obj.size >= size_min) : (obj.size == size_min) - @head.zip(obj[0..(@head.size - 1)]).all? {|pat, o| + + size_min = @head.size + @tail.size + if @rest + return false if obj.size < size_min + else + return false if obj.size != size_min + end + + return false unless @head.zip(obj[0..(@head.size - 1)]).all? {|pat, o| pat.execute(mmatch, o) - } && - @tail.zip(obj[(-@tail.size)..-1]).all? {|pat, o| + } + + return false unless @tail.zip(obj[(-@tail.size)..-1]).all? {|pat, o| pat.execute(mmatch, o) - } && - (!@rest || @rest.execute(mmatch, obj[@head.size..-(@tail.size+1)])) + } + + !@rest || @rest.execute(mmatch, obj[@head.size..-(@tail.size+1)]) end def inspect @@ -104,40 +243,59 @@ def compile_internal(free_index, target_name = "_obj") srcs << "#{target_name}.is_a?(::Array)" - size_min = @head.size + @tail.size - if @rest - srcs << "#{target_name}.size >= #{size_min}" - else - srcs << "#{target_name}.size == #{size_min}" - end + i = compile_size_check(target_name, srcs, ctxs, i) - elm_target_name = "#{target_name}_elm" - @head.each_with_index do|h, hi| - s, c, i = h.compile_internal(i, elm_target_name) - srcs << "(#{elm_target_name} = #{target_name}[#{hi}]; #{s})" - ctxs << c - end + i = compile_part(target_name, @head, srcs, ctxs, i) - srcs << "(#{target_name}_t = #{target_name}[(-#{@tail.size})..-1]; true)" - @tail.each_with_index do|t, ti| - s, c, i = t.compile_internal(i, elm_target_name) - srcs << "(#{elm_target_name} = #{target_name}_t[#{ti}]; #{s})" - ctxs << c + unless @tail.empty? + srcs << "#{target_name}_t = #{target_name}[(-#{@tail.size})..-1]; true" + i = compile_part("#{target_name}_t", @tail, srcs, ctxs, i) end - if @rest - tname = "#{target_name}_r" - s, c, i = @rest.compile_internal(i, tname) - srcs << "(#{tname} = #{target_name}[#{@head.size}..-(#{@tail.size+1})];#{s})" - ctxs << c - end + i = compile_rest(target_name, srcs, ctxs, i) [ - srcs.map{|s| "(#{s})"}.join(" &&\n"), + srcs.compact.map{|s| "(#{s})"}.join(" &&\n"), ctxs.flatten(1), i ] end + + private + def compile_size_check(target_name, srcs, ctxs, i) + size_min = @head.size + @tail.size + if @rest + srcs << "#{target_name}.size >= #{size_min}" + else + srcs << "#{target_name}.size == #{size_min}" + end + i + end + + def compile_part(target_name, part, srcs, ctxs, i) + part.each_with_index do|h, hi| + if h.is_a?(Obj) + s, c, i = h.compile_internal(i, "#{target_name}[#{hi}]") + srcs << "#{s}" if s + ctxs << c + else + elm_target_name = "#{target_name}_elm" + s, c, i = h.compile_internal(i, elm_target_name) + srcs << "#{elm_target_name} = #{target_name}[#{hi}]; #{s}" if s + ctxs << c + end + end + i + end + + def compile_rest(target_name, srcs, ctxs, i) + return i unless @rest + tname = "#{target_name}_r" + s, c, i = @rest.compile_internal(i, tname) + srcs << "#{tname} = #{target_name}[#{@head.size}..-(#{@tail.size+1})];#{s}" if s + ctxs << c + i + end end class ArrRest < self @@ -150,7 +308,7 @@ def rest? def inspect; "..."; end def compile_internal(free_index, target_name = "_obj") [ - "true", + nil, [], free_index ] @@ -171,10 +329,11 @@ def inspect end def compile_internal(free_index, target_name = "_obj") + val_src, c, i = Util.compile_value(@obj, free_index) [ - "_ctx[#{free_index}] === #{target_name}", - [@obj], - free_index + 1, + "#{val_src} === #{target_name}", + c, + i ] end end @@ -184,26 +343,92 @@ def execute(match, obj); true; end def inspect; 'ANY'; end def compile_internal(free_index, target_name = "_obj") [ - "true", + nil, [], free_index ] end end - class Group < self - def initialize(index) - @index = index + class Struct < self + def initialize(klass, pat) + @klass, @pat = klass, pat end - attr_reader :index - def execute(mmatch, obj) - mmatch[@index] = obj + + def inspect + "STRUCT(#{@klass.name || ""}, #{@pat.inspect})" + end + + def execute(match, obj) + obj.is_a?(@klass) && @pat.all?{|k, v| v.execute(match, obj[k]) } + end + + def compile_internal(free_index, target_name = "_obj") + srcs = [] + ctxs = [] + i = free_index + + if @klass.name + srcs << "#{target_name}.is_a?(::#{@klass.name})" + else + srcs << "#{target_name}.is_a?(_ctx[#{i}])" + ctxs << [@klass] + i += 1 + end + + @pat.each do|(member, v)| + s, c, i = v.compile_internal(i, "#{target_name}_elm") + srcs << "#{target_name}_elm = #{target_name}.#{member}; #{s}" if s + ctxs << c + end + + [ + srcs.map{|s| "(#{s})"}.join(" &&\n"), + ctxs.flatten(1), + i + ] + end + + class Builder + def initialize(klass) + raise ArgumentError, "#{klass} is not Struct" unless klass.ancestors.include?(::Struct) + @klass = klass + end + + # member_1_pat -> member_2_pat -> ... -> Pattern + # {member:Symbol => pattern} -> Pattern + def call(*args) + if args.size == 1 && args.first.is_a?(::Hash) + hash = args.first + hash.keys.each do|k| + raise ArgumentError, "#{k.inspect} is not member of #{@klass}" unless @klass.members.include?(k) + end + Struct.new(@klass, hash.each_with_object({}){|(k, plain), h| + h[k] = Pattern.build_from(plain) + }) + else + raise ArgumentError, "Member size not match: expected #{@klass.members.size} but #{args.size}" unless args.size == @klass.members.size + Struct.new(@klass, ::Hash[*@klass.members.zip(args.map{|a| Pattern.build_from(a) }).flatten(1)]) + end + end + end + end + + class Named < self + def initialize(name) + raise ::ArgumentError unless name.is_a?(Symbol) || name.is_a?(Numeric) + @name = name + end + attr_reader :name + alias index name # compatibility + def execute(match, obj) + match[@name] = obj true end - def inspect; "GROUP(#{@index})"; end + def inspect; "NAMED(#{@name})"; end def compile_internal(free_index, target_name = "_obj") [ - "_match[#{@index}] = #{target_name}; true", + "_match[#{@name.inspect}] = #{target_name}; true", [], free_index ] @@ -211,7 +436,8 @@ def compile_internal(free_index, target_name = "_obj") end class LogicalOp < self - def initialize(pats, op_str) + def initialize(name, pats, op_str) + @name = name @pats = pats @op_str = op_str end @@ -221,55 +447,54 @@ def compile_internal(free_index, target_name = "_obj") ctxs = [] @pats.each do|pat| s, c, i = pat.compile_internal(i, target_name) - srcs << s + if !s && @op_str == '||' # dirty... + srcs << 'true' + else + srcs << s + end ctxs << c end [ - srcs.map{|s| "(#{s})" }.join(" #{@op_str}\n"), + srcs.compact.map{|s| "(#{s})" }.join(" #{@op_str}\n"), ctxs.flatten(1), i ] end + def rest? + @pats.any?(&:rest?) + end + def opt? + @pats.any?(&:opt?) + end + def inspect + "#{@name}(#{@pats.map(&:inspect).join(',')})" + end end class Or < LogicalOp def initialize(pats) - super(pats, '||') + super('OR', pats, '||') end def execute(mmatch, obj) @pats.any? do|pat| pat.execute(mmatch, obj) end end - def rest? - @pats.any?(&:rest?) - end - def inspect - "OR(#{@pats.map(&:inspect).join(',')})" - end end class And < LogicalOp def initialize(pats) - super(pats, '&&') + super('AND', pats, '&&') end def execute(mmatch, obj) @pats.all? do|pat| pat.execute(mmatch, obj) end end - def rest? - @pats.any?(&:rest?) - end - def inspect - "AND(#{@pats.map(&:inspect).join(',')})" - end end end - GROUP = 100.times.map{|i| Pattern::Group.new(i) } - def self.or(*pats) Pattern::Or.new(pats.map{|p| Pattern.build_from(p) }) end @@ -282,10 +507,32 @@ def self._xs @xs = Pattern::ArrRest.new end + # Use in hash value. + # Mark this pattern is optional. + def self.opt(pat = _any) + Pattern::Opt.new(Pattern.build_from(pat)) + end + + EXACT = Object.new + def EXACT.inspect + "EXACT" + end + # Use in Hash key. + # Specify exact match or not. + def self.exact + EXACT + end + + def self.[](struct_klass) + Pattern::Struct::Builder.new(struct_klass) + end + + PREDEF_GROUP_SIZE = 100 + class < Proc } + def initialize(&block) + # [[Pattern, Proc]...] @rules = [] - @else = ->(obj){nil} + @else = nil block[self] end def on(pat, &block) - if @compile - @rules << [Pattern.build_from(pat).compile, block] - else - @rules << [Pattern.build_from(pat), block] - end + @rules << [Pattern.build_from(pat), block] end def else(&block) @@ -322,36 +564,82 @@ def apply(obj, _self = nil) return block.call(match, _self) end end - @else[obj, _self] + @else ? @else[obj, _self] : (raise NoMatchError.new(obj)) + end + + def inspect + "Rule{#{@rules.map(&:first).map(&:inspect).join(', ')}#{@else ? ', _' : ''}}" end - end - class RuleCache - def initialize(compile = true) - @compile = compile - @rules = {} + def compile_call(block, *args) + "call(#{args[0...block.arity].join(', ')})" end - def match(rule_name, obj, _self = nil, &rule) - (@rules[rule_name] ||= ::Patm::Rule.new(@compile, &rule)).apply(obj, _self) + + def compile + i = 0 + ctxs = [] + srcs = [] + @rules.each do|pat, block| + s, c, i = pat.compile_internal(i, '_obj') + ctxs << c + ctxs << [block] + srcs << "if (#{s || 'true'})\n_ctx[#{i}].#{compile_call(block, "::Patm::Match.new(_match)"," _self")}" + i += 1 + end + src = srcs.join("\nels") + if @else + src << "\nelse\n" unless srcs.empty? + src << "_ctx[#{i}].#{compile_call(@else, "_obj"," _self")}" + ctxs << [@else] + i += 1 + else + src << "\nelse\n" unless srcs.empty? + src << "raise ::Patm::NoMatchError.new(_obj)" + end + src << "\nend" unless srcs.empty? + Compiled.new( + src, + ctxs.flatten(1) + ) + end + + class Compiled + def initialize(src_body, context) + @src_body = src_body + @context = context + @src = <<-RUBY + def apply(_obj, _self = nil) + _ctx = @context + _match = {} +#{@src_body} + end + RUBY + + singleton_class = class < 0.7.1') + s.add_development_dependency('simplecov-vim') s.add_development_dependency('rspec', '~>2.14') s.add_development_dependency('pry', '~>0.9') + s.add_development_dependency('pattern-match', '=0.5.1') # for benchmarking end diff --git a/vendor/patm/spec/patm_spec.rb b/vendor/patm/spec/patm_spec.rb index f232ae3..1244bcd 100644 --- a/vendor/patm/spec/patm_spec.rb +++ b/vendor/patm/spec/patm_spec.rb @@ -1,37 +1,65 @@ +require 'simplecov' +require 'simplecov-vim/formatter' +SimpleCov.start do + formatter SimpleCov::Formatter::VimFormatter + formatter SimpleCov::Formatter::HTMLFormatter +end + require File.join(File.dirname(__FILE__), '..', 'lib', 'patm.rb') require 'pry' module PatmHelper - extend RSpec::Matchers::DSL + module Pattern + extend RSpec::Matchers::DSL - matcher :these_matches do|*matches| - match do|actual| - matches.all?{|m| m.matches?(actual) } + matcher :these_matches do|*matches| + match do|actual| + matches.all?{|m| m.matches?(actual) } + end end - end - matcher :match_to do|expected| - match do|actual| - exec(actual, expected) - end + matcher :match_to do|expected| + match do|actual| + exec(actual, expected) + end - def exec(actual, expected) - @match = Patm::Match.new - actual.execute(@match, expected) - end + def exec(actual, expected) + @match = Patm::Match.new + actual.execute(@match, expected) + end + + def match; @match; end + + def and_capture(g1, g2 = nil, g3 = nil, g4 = nil) + these_matches( + self, _capture(self, {1 => g1, 2 => g2, 3 => g3, 4 => g4}) + ) + end - def match; @match; end + def and_named_capture(capture) + these_matches( + self, _capture(self, capture) + ) + end + end - def and_capture(g1, g2 = nil, g3 = nil, g4 = nil) - these_matches( - self, _capture(self, g1, g2, g3, g4) - ) + matcher :_capture do|m, capture| + match do|_| + [m.match[1], m.match[2], m.match[3], m.match[4]] == capture.values_at(1,2,3,4) + end end end - matcher :_capture do|m, g1, g2, g3, g4| - match do|_| - [m.match[1], m.match[2], m.match[3], m.match[4]] == [g1, g2, g3, g4] + module Rule + extend RSpec::Matchers::DSL + matcher :converts do|value, result| + match do|rule| + @matched = rule.apply(value, nil) + @matched == result + end + failure_message_for_should do|rule| + "match #{value.inspect} to #{rule.inspect}: expected #{result} but #{@matched.inspect}" + end end end @@ -51,7 +79,7 @@ def and_capture(g1, g2 = nil, g3 = nil, g4 = nil) it 'with predefined Rule' do p = Patm - r = p::Rule.new(false) do|r| + r = p::Rule.new do|r| r.on [1, p._1, p._2] do|m| [m._1, m._2] end @@ -62,43 +90,13 @@ def and_capture(g1, g2 = nil, g3 = nil, g4 = nil) it 'with predefined Rule(compiled)' do p = Patm - r = p::Rule.new(true) do|r| - r.on [1, p._1, p._2] do|m| - [m._1, m._2] - end - r.else {|obj| [] } - end - r.apply([1, 2, 3]).should == [2, 3] - end - - it 'with RuleCache' do - p = Patm - rs = p::RuleCache.new - - rs.match(:pattern_1, [1, 2, 3]) do|r| - r.on [1, p._1, p._2] do|m| - [m._1, m._2] - end - r.else {|obj| [] } - end - .should == [2, 3] - - rs.match(:pattern_1, [1, 3, 5]) {|r| fail "should not reach here" }.should == [3, 5] - end - - it 'with RuleCache(compiled)' do - p = Patm - rs = p::RuleCache.new(true) - - rs.match(:pattern_1, [1, 2, 3]) do|r| + r = p::Rule.new do|r| r.on [1, p._1, p._2] do|m| [m._1, m._2] end r.else {|obj| [] } end - .should == [2, 3] - - rs.match(:pattern_1, [1, 3, 5]) {|r| fail "should not reach here" }.should == [3, 5] + r.compile.apply([1, 2, 3]).should == [2, 3] end it 'with DSL' do @@ -133,8 +131,55 @@ class <(r){ + r.on([1, Patm._1, Patm._2]) {|m| [m._1, m._2] } + r.else { [] } + }) do + it { should converts([1, 2, 3], [2, 3]) } + it { should converts([1], []) } + end + + rule(:rule2, ->(r) { + r.on(1) { 100 } + }) do + it { should converts(1, 100) } + it { expect { subject.apply(nil) }.to raise_error(Patm::NoMatchError) } + end + + context 'regression' do + rule(:reg1, ->(r){ + _1, _2 = Patm._1, Patm._2 + r.on([1, _1, 2]) {|m| m._1 } + r.on([1, _1, _2]) {|m| [m._1, m._2] } + r.on(nil) { 100 } + r.else { nil } + }) do + it { should converts([1, 2, 2], 2) } + it { should converts([1, 2, 3], [2, 3]) } + it { should converts(nil, 100) } + it { should converts([], nil) } + end + + rule(:reg2, ->(r) { r.else { nil }}) do + it { should converts(1, nil) } + it { should converts("hoge", nil) } + end + end +end + describe Patm::Pattern do - include PatmHelper + include PatmHelper::Pattern def self.pattern(plain, &b) context "pattern '#{plain.inspect}'" do subject { Patm::Pattern.build_from(plain) } @@ -206,9 +251,9 @@ def self.pattern(plain, &b) it { should match_to([0, 1, 2, 3]).and_capture([2, 3]) } end - pattern [0, 1, Patm._xs, 2] do - it { should match_to([0,1,2]) } - it { should match_to([0,1,10,20,30,2]) } + pattern [0, 1, Patm._xs[1], 2] do + it { should match_to([0,1,2]).and_capture([]) } + it { should match_to([0,1,10,20,30,2]).and_capture([10,20,30]) } it { should_not match_to([0,1]) } end @@ -217,6 +262,65 @@ def self.pattern(plain, &b) it { should_not match_to [0, [1, 3]] } end + pattern Patm._any[:x] do + it { should match_to("aaa").and_named_capture(:x => "aaa") } + end + + pattern(a: Patm._any[1]) do + it { should_not match_to {} } + it { should match_to(a: 1).and_capture(1) } + it { should match_to(a: 1, b: 2).and_capture(1) } + end + + pattern(a: Patm._any, Patm.exact => false) do + it { should_not match_to(b: 1) } + it { should match_to(a: 1) } + it { should match_to(a: 1, b: 2) } + end + + pattern(a: Patm._any, Patm.exact => true) do + it { should_not match_to(b: 1) } + it { should match_to(a: 1) } + it { should_not match_to(a: 1, b: 2) } + end + + pattern(a: Patm._any[1].opt) do + it { should match_to({}).and_capture(nil) } + it { should match_to({a: 1}).and_capture(1) } + end + + pattern(a: Patm._any[1], b: Patm._any[2].opt) do + it { should_not match_to({}) } + it { should match_to({a: 1}).and_capture(1, nil) } + it { should match_to({a: 1, b: 2}).and_capture(1, 2) } + end + + pattern({a: 1} => {b: 2}) do + it { should_not match_to({a: 1} => {b: 0}) } + it { should_not match_to({a: 0} => {b: 2}) } + it { should match_to({a: 1} => {b: 2}) } + end + + Struct1 = Struct.new(:a, :b) + + pattern(Patm[Struct1].(1, Patm._1)) do + it { should_not match_to(nil) } + it { should_not match_to(Struct1.new(2, 2)) } + it { should match_to(Struct1.new(1, 2)).and_capture(2) } + end + + pattern(Patm[Struct1].(a: 1, b: Patm._1)) do + it { should_not match_to(nil) } + it { should_not match_to(Struct1.new(2, 2)) } + it { should match_to(Struct1.new(1, 2)).and_capture(2) } + end + + unnamed_struct = Struct.new(:x, :y) + pattern(Patm[unnamed_struct].(x: 1, y: Patm._1)) do + it { should_not match_to(nil) } + it { should match_to(unnamed_struct.new(1, 2)).and_capture(2) } + end + context 'regression' do pattern [:assign, [:var_field, [:@ident, Patm._1, [Patm._2, Patm._3]]], Patm._4] do it { should match_to([:assign, [:var_field, [:@ident, 10, [20, 30]]], false]).and_capture(10, 20, 30, false) } @@ -225,5 +329,17 @@ def self.pattern(plain, &b) it { should match_to [1] } it { should match_to [2] } end + pattern [Patm._1, 2, [Patm._any, 3], Patm._xs[1], 4] do + it { should match_to([1, 2, [10, 3], 4]).and_capture([]) } + it { should match_to([1, 2, [10, 3], 20, 4]).and_capture([20]) } + end + pattern Patm._1&Array do + it { should_not match_to(1) } + it { should match_to([]).and_capture([]) } + end + pattern Patm.or(1, Patm._any) do + it { should match_to(1) } + it { should match_to(999) } + end end end