PATM: PATtern Matcher for Ruby

Build Status Code Climate

PATM is extremely faster pattern match library.

Features: Match value/classes, Capture, Array/Hash decomposition.

Usage

require 'patm'
# 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]
# With case(simple but slow)
def match(obj)
  p = Patm
  _xs = Patm._xs
  case obj
  when m = Patm.match([:x, p._1, p._2])
    [m._2, m._1]
  when m = Patm.match([1, _xs&p._1])
    m._1
  end
end

match([1, 2, 3])
# => [2, 3]

match([:x, :y, :z])
# => [:z, :y]

match([])
# => nil
# With pre-built Rule
rule = Patm::Rule.new do|r|
  p = Patm
  _xs = Patm._xs
  r.on [:x, p._1, p._2] do|m|
    [m._2, m._1]
  end
  r.on [1, _xs&p._1] do|m|
    m._1
  end
end

rule.apply([1,2,3])
# => [2, 3]

rule.apply([:x, :y, :z])
# => [:z, :y]

rule.apply([])
# => nil

DSL

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

matcher = PatternMatcher.new

matcher.match(1)

Patterns

Value

Value patterns such as 1, :x, String, ... matches if pattern === target_value is true.

Struct

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 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)

Changes

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
  • Change interface

0.1.0

  • Faster matching with pattern compilation
  • Fix StackOverflow bug for [Patm.or()]

0.0.1

  • Initial release