@@ -125,10 +125,11 @@ class Compiler
125
125
STRING = /".+?"/ . freeze
126
126
METHOD_NAME = /\# ?#{ IDENTIFIER } [!?]?\( ?/ . freeze
127
127
PARAM_CONST = /%[A-Z:][a-zA-Z_:]+/ . freeze
128
+ KEYWORD_NAME = /%[a-z_]+/ . freeze
128
129
PARAM_NUMBER = /%\d */ . freeze
129
130
130
131
SEPARATORS = /\s +/ . freeze
131
- TOKENS = Regexp . union ( META , PARAM_CONST , PARAM_NUMBER , NUMBER ,
132
+ TOKENS = Regexp . union ( META , PARAM_CONST , KEYWORD_NAME , PARAM_NUMBER , NUMBER ,
132
133
METHOD_NAME , SYMBOL , STRING )
133
134
134
135
TOKEN = /\G (?:#{ SEPARATORS } |#{ TOKENS } |.)/ . freeze
@@ -141,6 +142,7 @@ class Compiler
141
142
LITERAL = /\A (?:#{ SYMBOL } |#{ NUMBER } |#{ STRING } )\Z / . freeze
142
143
PARAM = /\A #{ PARAM_NUMBER } \Z / . freeze
143
144
CONST = /\A #{ PARAM_CONST } \Z / . freeze
145
+ KEYWORD = /\A #{ KEYWORD_NAME } \Z / . freeze
144
146
CLOSING = /\A (?:\) |\} |\] )\Z / . freeze
145
147
146
148
REST = '...'
@@ -199,6 +201,7 @@ def initialize(str, node_var = 'node0')
199
201
@captures = 0 # number of captures seen
200
202
@unify = { } # named wildcard -> temp variable
201
203
@params = 0 # highest % (param) number seen
204
+ @keywords = Set [ ] # keyword parameters seen
202
205
run ( node_var )
203
206
end
204
207
@@ -238,6 +241,7 @@ def compile_expr(token = tokens.shift)
238
241
when LITERAL then compile_literal ( token )
239
242
when PREDICATE then compile_predicate ( token )
240
243
when NODE then compile_nodetype ( token )
244
+ when KEYWORD then compile_keyword ( token [ 1 ..-1 ] )
241
245
when CONST then compile_const ( token [ 1 ..-1 ] )
242
246
when PARAM then compile_param ( token [ 1 ..-1 ] )
243
247
when CLOSING then fail_due_to ( "#{ token } in invalid position" )
@@ -626,6 +630,10 @@ def compile_const(const)
626
630
"#{ const } === #{ CUR_ELEMENT } "
627
631
end
628
632
633
+ def compile_keyword ( keyword )
634
+ "#{ get_keyword ( keyword ) } === #{ CUR_ELEMENT } "
635
+ end
636
+
629
637
def compile_args ( tokens )
630
638
index = tokens . find_index { |token | token == ')' }
631
639
@@ -661,6 +669,11 @@ def get_param(number)
661
669
number . zero? ? @root : "param#{ number } "
662
670
end
663
671
672
+ def get_keyword ( name )
673
+ @keywords << name
674
+ name
675
+ end
676
+
664
677
def emit_yield_capture ( when_no_capture = '' )
665
678
yield_val = if @captures . zero?
666
679
when_no_capture
@@ -686,9 +699,15 @@ def emit_param_list
686
699
( 1 ..@params ) . map { |n | "param#{ n } " } . join ( ',' )
687
700
end
688
701
689
- def emit_trailing_params
702
+ def emit_keyword_list ( forwarding : false )
703
+ pattern = "%<keyword>s: #{ '%<keyword>s' if forwarding } "
704
+ @keywords . map { |k | format ( pattern , keyword : k ) } . join ( ',' )
705
+ end
706
+
707
+ def emit_trailing_params ( forwarding : false )
690
708
params = emit_param_list
691
- params . empty? ? '' : ",#{ params } "
709
+ keywords = emit_keyword_list ( forwarding : forwarding )
710
+ [ params , keywords ] . reject ( &:empty? ) . map { |p | ", #{ p } " } . join
692
711
end
693
712
694
713
def emit_method_code
@@ -788,7 +807,7 @@ def emit_node_search(method_name)
788
807
else
789
808
prelude = <<~RUBY
790
809
return enum_for(:#{ method_name } ,
791
- node0#{ emit_trailing_params } ) unless block_given?
810
+ node0#{ emit_trailing_params ( forwarding : true ) } ) unless block_given?
792
811
RUBY
793
812
on_match = emit_yield_capture ( 'node' )
794
813
end
@@ -845,11 +864,15 @@ def initialize(str)
845
864
instance_eval ( src , __FILE__ , __LINE__ + 1 )
846
865
end
847
866
848
- def match ( *args )
867
+ def match ( *args , ** rest )
849
868
# If we're here, it's because the singleton method has not been defined,
850
869
# either because we've been dup'ed or serialized through YAML
851
870
initialize ( pattern )
852
- match ( *args )
871
+ if rest . empty?
872
+ match ( *args )
873
+ else
874
+ match ( *args , **rest )
875
+ end
853
876
end
854
877
855
878
def marshal_load ( pattern )
0 commit comments