kschiess / parslet

A small PEG based parser library. See the Hacking page in the Wiki as well.
kschiess.github.com/parslet
MIT License
809 stars 95 forks source link

Transform: Match sequence of hashes #52

Closed trevorfancher closed 13 years ago

trevorfancher commented 13 years ago

More than once in writing my parsers I hit the issue of finding it easiest to generate an array of hashes, such as:

    rule(:var_expr) {

        init = str('=') >> space >> expression
        var = ident.as(:var_name) >> space >> init.maybe.as(:var_init)
        list = str(',') >> space >> var

        str('var') >> space >> (var >> list.repeat(0)).as(:var_list) >> space >>
        str('in') >> space >> expression.as(:var_expr)
    }

Notice var_list will consist of an Array of Hash's in the parse tree. This means var_list can't be matched by the 'sequence' matcher. Since I hit this issue more than once I decided extend Parslet with this 'hash_sequence' matcher:

module MyTransformers
    class HashSequenceBind < Parslet::Pattern::SubtreeBind

        def initialize(symbol, keys, key_binder=nil)
            self.symbol = symbol
            @keys = keys
            @key_binder = key_binder || Parslet::Pattern::SimpleBind.new
        end

        def can_bind?(subtree)
            subtree.kind_of?(Array) and
                subtree.all? { |el|
                    el.is_a?(Hash) and
                    el.keys == @keys and
                    el.all? { |k, v|
                        @key_binder.can_bind? v
                    }
                }
        end
    end
end

module Parslet
    def hash_sequence(symbol, keys)
        MyTransformers::HashSequenceBind.new(symbol, keys)
    end
    module_function :hash_sequence
end

Used in a transform like:

    rule(hash_sequence(:var_list, [:var_name, :var_init]),
             simple(:var_expr)) {
        VarExpr.new(var_list, var_expr)
    }

Notice var_list will match only an Array whose elements are all Hash's with the keys :var_name, and :var_init, and then only if all those values are matched by Parslet::Pattern::SimpleBind. This last behavior is modifiable by the optional third parameter to hash_sequence.

I've used this feature more than once and will make a patch and pull request if others find it useful.

kschiess commented 13 years ago

I don't see why this is an issue. Please write to the mailing list for discussing possible new features.