tsearch 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/texticle.rb +78 -0
- data/lib/texticle/full_text_index.rb +54 -0
- data/lib/texticle/nodes/and_term_node.rb +5 -0
- data/lib/texticle/nodes/boolean_term_node.rb +2 -0
- data/lib/texticle/nodes/expression_node.rb +17 -0
- data/lib/texticle/nodes/grouped_term_node.rb +15 -0
- data/lib/texticle/nodes/not_term_node.rb +5 -0
- data/lib/texticle/nodes/or_term_node.rb +5 -0
- data/lib/texticle/nodes/query_node.rb +8 -0
- data/lib/texticle/nodes/quoted_term_node.rb +5 -0
- data/lib/texticle/nodes/term_node.rb +23 -0
- data/lib/texticle/nodes/word_node.rb +9 -0
- data/lib/texticle/parser.rb +53 -0
- data/lib/texticle/tasks.rb +55 -0
- data/lib/texticle/tquery.rb +762 -0
- data/lib/texticle/tquery.treetop +66 -0
- data/rails/init.rb +3 -0
- data/test/helper.rb +43 -0
- data/test/test_full_text_index.rb +74 -0
- data/test/test_texticle.rb +60 -0
- metadata +81 -0
data/lib/texticle.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'texticle/full_text_index'
|
2
|
+
|
3
|
+
####
|
4
|
+
# Texticle exposes full text search capabilities from PostgreSQL, and allows
|
5
|
+
# you to declare full text indexes. Texticle will extend ActiveRecord with
|
6
|
+
# named_scope methods making searching easy and fun!
|
7
|
+
#
|
8
|
+
# Texticle.index is automatically added to ActiveRecord::Base.
|
9
|
+
#
|
10
|
+
# To declare an index on a model, just use the index method:
|
11
|
+
#
|
12
|
+
# class Product < ActiveRecord::Base
|
13
|
+
# index do
|
14
|
+
# name
|
15
|
+
# description
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# This will allow you to do full text search on the name and description
|
20
|
+
# columns for the Product model. It defines a named_scope method called
|
21
|
+
# "search", so you can take advantage of the search like this:
|
22
|
+
#
|
23
|
+
# Product.search('foo bar')
|
24
|
+
#
|
25
|
+
# Indexes may also be named. For example:
|
26
|
+
#
|
27
|
+
# class Product < ActiveRecord::Base
|
28
|
+
# index 'author' do
|
29
|
+
# name
|
30
|
+
# author
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# A named index will add a named_scope with the index name prefixed by
|
35
|
+
# "search". In order to take advantage of the "author" index, just call:
|
36
|
+
#
|
37
|
+
# Product.search_author('foo bar')
|
38
|
+
#
|
39
|
+
# Finally, column names can be ranked. The ranks are A, B, C, and D. This
|
40
|
+
# lets us declare that matches in the "name" column are more important
|
41
|
+
# than matches in the "description" column:
|
42
|
+
#
|
43
|
+
# class Product < ActiveRecord::Base
|
44
|
+
# index do
|
45
|
+
# name 'A'
|
46
|
+
# description 'B'
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
module Texticle
|
50
|
+
# The version of Texticle you are using.
|
51
|
+
VERSION = '1.0.2'
|
52
|
+
|
53
|
+
# A list of full text indexes
|
54
|
+
attr_accessor :full_text_indexes
|
55
|
+
|
56
|
+
###
|
57
|
+
# Create an index with +name+ using +dictionary+
|
58
|
+
def index name = nil, dictionary = 'english', &block
|
59
|
+
search_name = ['search', name].compact.join('_')
|
60
|
+
|
61
|
+
class_eval do
|
62
|
+
named_scope search_name.to_sym, lambda { |term|
|
63
|
+
p = Texticle::Parser.new
|
64
|
+
term = p.parse(term)
|
65
|
+
{
|
66
|
+
:select => "#{table_name}.*, ts_rank_cd((#{full_text_indexes.first.to_s}),
|
67
|
+
to_tsquery(#{connection.quote(term)})) as rank",
|
68
|
+
:conditions =>
|
69
|
+
["#{full_text_indexes.first.to_s} @@ to_tsquery(?)", term],
|
70
|
+
:order => 'rank DESC'
|
71
|
+
}
|
72
|
+
}
|
73
|
+
end
|
74
|
+
index_name = [table_name, name, 'fts_idx'].compact.join('_')
|
75
|
+
(self.full_text_indexes ||= []) <<
|
76
|
+
FullTextIndex.new(index_name, dictionary, self, &block)
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Texticle
|
2
|
+
class FullTextIndex # :nodoc:
|
3
|
+
attr_accessor :index_columns
|
4
|
+
|
5
|
+
def initialize name, dictionary, model_class, &block
|
6
|
+
@name = name
|
7
|
+
@dictionary = dictionary
|
8
|
+
@model_class = model_class
|
9
|
+
@index_columns = {}
|
10
|
+
@string = nil
|
11
|
+
instance_eval(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def create
|
15
|
+
@model_class.connection.execute create_sql
|
16
|
+
end
|
17
|
+
|
18
|
+
def destroy
|
19
|
+
@model_class.connection.execute destroy_sql
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_sql
|
23
|
+
<<-eosql
|
24
|
+
CREATE index #{@name}
|
25
|
+
ON #{@model_class.table_name}
|
26
|
+
USING gin((#{to_s}))
|
27
|
+
eosql
|
28
|
+
end
|
29
|
+
|
30
|
+
def destroy_sql
|
31
|
+
"DROP index IF EXISTS #{@name}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
return @string if @string
|
36
|
+
vectors = []
|
37
|
+
@index_columns.sort_by { |k,v| k }.each do |weight, columns|
|
38
|
+
c = columns.map { |x| "coalesce(#{@model_class.table_name}.#{x}, '')" }
|
39
|
+
if weight == 'none'
|
40
|
+
vectors << "to_tsvector('#{@dictionary}', #{c.join(" || ' ' || ")})"
|
41
|
+
else
|
42
|
+
vectors <<
|
43
|
+
"setweight(to_tsvector('#{@dictionary}', #{c.join(" || ' ' || ")}), '#{weight}')"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
@string = vectors.join(" || ' ' || ")
|
47
|
+
end
|
48
|
+
|
49
|
+
def method_missing name, *args
|
50
|
+
weight = args.shift || 'none'
|
51
|
+
(index_columns[weight] ||= []) << name.to_s
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class ExpressionNode < Treetop::Runtime::SyntaxNode
|
2
|
+
def to_tquery
|
3
|
+
buffer = []
|
4
|
+
elements[0].elements.each do |elem|
|
5
|
+
if elem.respond_to?(:to_tquery)
|
6
|
+
t = elem.to_tquery
|
7
|
+
if t =~ /^\s+(\||&).*$/ || buffer.empty?
|
8
|
+
buffer << t
|
9
|
+
else
|
10
|
+
buffer << " | " + t
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
buffer.join(' ')
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class GroupedTermNode < Treetop::Runtime::SyntaxNode
|
2
|
+
def to_tquery
|
3
|
+
buffer = []
|
4
|
+
elements[1].elements.each do |elem|
|
5
|
+
t = elem.to_tquery
|
6
|
+
if t =~ /^\s+(\||&).*$/ || buffer.empty?
|
7
|
+
buffer << t
|
8
|
+
else
|
9
|
+
buffer << " | " + t
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
'(' + buffer.join(' ') + ')'
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class TermNode < Treetop::Runtime::SyntaxNode
|
2
|
+
# module TermNode
|
3
|
+
def to_tquery
|
4
|
+
terms = []
|
5
|
+
|
6
|
+
return '' if elements.empty?
|
7
|
+
|
8
|
+
elements.each do |child_elem|
|
9
|
+
next if child_elem.elements.empty?
|
10
|
+
if child_elem.respond_to?(:to_tquery)
|
11
|
+
terms << child_elem.to_tquery
|
12
|
+
else
|
13
|
+
child_elem.elements.each do |grand_child_elem|
|
14
|
+
if grand_child_elem.respond_to?(:to_tquery)
|
15
|
+
terms << grand_child_elem.to_tquery
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
terms.join(' ')
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'treetop'
|
2
|
+
require File.dirname(__FILE__) + '/tquery'
|
3
|
+
|
4
|
+
Dir[File.expand_path(File.join(File.dirname(__FILE__),'nodes','**','*.rb'))].each {|f| require f}
|
5
|
+
|
6
|
+
module Texticle
|
7
|
+
class Parser
|
8
|
+
attr_accessor :parse_tree, :tquery
|
9
|
+
|
10
|
+
def parse(str)
|
11
|
+
s = str.strip
|
12
|
+
p = TQueryParser.new
|
13
|
+
@parse_tree = nil
|
14
|
+
@tquery = nil
|
15
|
+
return @tquery if s.blank?
|
16
|
+
failed = false
|
17
|
+
while @parse_tree.nil?
|
18
|
+
begin
|
19
|
+
@parse_tree = p.parse(s)
|
20
|
+
raise p.failure_reason if @parse_tree.nil?
|
21
|
+
result = parser_tree_to_tquery(@parse_tree)
|
22
|
+
break if effectively_empty?(result)
|
23
|
+
@tquery = result
|
24
|
+
rescue
|
25
|
+
raise $! if failed
|
26
|
+
failed = true
|
27
|
+
s = s.gsub(/[^A-Za-z0-9\s]/, '').strip
|
28
|
+
break if effectively_empty?(s)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
@tquery
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
# the search string is effectively empty (meaning tsearch will see it as blank)
|
36
|
+
def effectively_empty?(str)
|
37
|
+
s = str.gsub(/[^A-Za-z0-9\s]/, '').strip
|
38
|
+
unique_terms = s.downcase.split(/\s+/).uniq
|
39
|
+
s.blank? || unique_terms.reject {|e| e == 'and' || e == 'or'}.empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
def parser_tree_to_tquery(parse_tree)
|
43
|
+
results = if parse_tree.expression.nil?
|
44
|
+
''
|
45
|
+
else
|
46
|
+
parse_tree.expression.to_tquery
|
47
|
+
end
|
48
|
+
unless results.nil?
|
49
|
+
results.gsub(/^\s+(\||\&)/, '')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'texticle'
|
3
|
+
|
4
|
+
namespace :texticle do
|
5
|
+
desc "Create full text index migration"
|
6
|
+
task :migration => :environment do
|
7
|
+
now = Time.now.utc
|
8
|
+
filename = "#{now.strftime('%Y%m%d%H%m%S')}_full_text_search_#{now.to_i}.rb"
|
9
|
+
File.open(File.join(RAILS_ROOT, 'db', 'migrate', filename), 'wb') { |fh|
|
10
|
+
fh.puts "class FullTextSearch#{now.to_i} < ActiveRecord::Migration"
|
11
|
+
fh.puts " def self.up"
|
12
|
+
Dir[File.join(RAILS_ROOT, 'app', 'models', '*.rb')].each do |f|
|
13
|
+
klass = File.basename(f, '.rb').classify.constantize
|
14
|
+
if klass.respond_to?(:full_text_indexes)
|
15
|
+
(klass.full_text_indexes || []).each do |fti|
|
16
|
+
fh.puts <<-eostmt
|
17
|
+
ActiveRecord::Base.connection.execute(<<-'eosql')
|
18
|
+
#{fti.destroy_sql}
|
19
|
+
eosql
|
20
|
+
ActiveRecord::Base.connection.execute(<<-'eosql')
|
21
|
+
#{fti.create_sql}
|
22
|
+
eosql
|
23
|
+
eostmt
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
fh.puts " end"
|
28
|
+
fh.puts "end"
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "Create full text indexes"
|
33
|
+
task :create_indexes => ['texticle:destroy_indexes'] do
|
34
|
+
Dir[File.join(RAILS_ROOT, 'app', 'models', '*.rb')].each do |f|
|
35
|
+
klass = File.basename(f, '.rb').classify.constantize
|
36
|
+
if klass.respond_to?(:full_text_indexes)
|
37
|
+
(klass.full_text_indexes || []).each do |fti|
|
38
|
+
fti.create
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
desc "Destroy full text indexes"
|
45
|
+
task :destroy_indexes => [:environment] do
|
46
|
+
Dir[File.join(RAILS_ROOT, 'app', 'models', '*.rb')].each do |f|
|
47
|
+
klass = File.basename(f, '.rb').classify.constantize
|
48
|
+
if klass.respond_to?(:full_text_indexes)
|
49
|
+
(klass.full_text_indexes || []).each do |fti|
|
50
|
+
fti.destroy
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,762 @@
|
|
1
|
+
# Autogenerated from a Treetop grammar. Edits may be lost.
|
2
|
+
|
3
|
+
|
4
|
+
module TQuery
|
5
|
+
include Treetop::Runtime
|
6
|
+
|
7
|
+
def root
|
8
|
+
@root || :query
|
9
|
+
end
|
10
|
+
|
11
|
+
module Query0
|
12
|
+
def space1
|
13
|
+
elements[0]
|
14
|
+
end
|
15
|
+
|
16
|
+
def expression
|
17
|
+
elements[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def space2
|
21
|
+
elements[3]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def _nt_query
|
26
|
+
start_index = index
|
27
|
+
if node_cache[:query].has_key?(index)
|
28
|
+
cached = node_cache[:query][index]
|
29
|
+
if cached
|
30
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
31
|
+
@index = cached.interval.end
|
32
|
+
end
|
33
|
+
return cached
|
34
|
+
end
|
35
|
+
|
36
|
+
i0, s0 = index, []
|
37
|
+
r1 = _nt_space
|
38
|
+
s0 << r1
|
39
|
+
if r1
|
40
|
+
r2 = _nt_expression
|
41
|
+
s0 << r2
|
42
|
+
if r2
|
43
|
+
r4 = _nt_more_query
|
44
|
+
if r4
|
45
|
+
r3 = r4
|
46
|
+
else
|
47
|
+
r3 = instantiate_node(SyntaxNode,input, index...index)
|
48
|
+
end
|
49
|
+
s0 << r3
|
50
|
+
if r3
|
51
|
+
r5 = _nt_space
|
52
|
+
s0 << r5
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
if s0.last
|
57
|
+
r0 = instantiate_node(QueryNode,input, i0...index, s0)
|
58
|
+
r0.extend(Query0)
|
59
|
+
else
|
60
|
+
@index = i0
|
61
|
+
r0 = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
node_cache[:query][start_index] = r0
|
65
|
+
|
66
|
+
r0
|
67
|
+
end
|
68
|
+
|
69
|
+
module MoreQuery0
|
70
|
+
def space1
|
71
|
+
elements[0]
|
72
|
+
end
|
73
|
+
|
74
|
+
def query
|
75
|
+
elements[1]
|
76
|
+
end
|
77
|
+
|
78
|
+
def space2
|
79
|
+
elements[2]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def _nt_more_query
|
84
|
+
start_index = index
|
85
|
+
if node_cache[:more_query].has_key?(index)
|
86
|
+
cached = node_cache[:more_query][index]
|
87
|
+
if cached
|
88
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
89
|
+
@index = cached.interval.end
|
90
|
+
end
|
91
|
+
return cached
|
92
|
+
end
|
93
|
+
|
94
|
+
i0, s0 = index, []
|
95
|
+
r1 = _nt_space
|
96
|
+
s0 << r1
|
97
|
+
if r1
|
98
|
+
r2 = _nt_query
|
99
|
+
s0 << r2
|
100
|
+
if r2
|
101
|
+
r3 = _nt_space
|
102
|
+
s0 << r3
|
103
|
+
end
|
104
|
+
end
|
105
|
+
if s0.last
|
106
|
+
r0 = instantiate_node(QueryNode,input, i0...index, s0)
|
107
|
+
r0.extend(MoreQuery0)
|
108
|
+
else
|
109
|
+
@index = i0
|
110
|
+
r0 = nil
|
111
|
+
end
|
112
|
+
|
113
|
+
node_cache[:more_query][start_index] = r0
|
114
|
+
|
115
|
+
r0
|
116
|
+
end
|
117
|
+
|
118
|
+
module Expression0
|
119
|
+
def space
|
120
|
+
elements[1]
|
121
|
+
end
|
122
|
+
|
123
|
+
def more
|
124
|
+
elements[2]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def _nt_expression
|
129
|
+
start_index = index
|
130
|
+
if node_cache[:expression].has_key?(index)
|
131
|
+
cached = node_cache[:expression][index]
|
132
|
+
if cached
|
133
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
134
|
+
@index = cached.interval.end
|
135
|
+
end
|
136
|
+
return cached
|
137
|
+
end
|
138
|
+
|
139
|
+
i0, s0 = index, []
|
140
|
+
s1, i1 = [], index
|
141
|
+
loop do
|
142
|
+
r2 = _nt_term
|
143
|
+
if r2
|
144
|
+
s1 << r2
|
145
|
+
else
|
146
|
+
break
|
147
|
+
end
|
148
|
+
end
|
149
|
+
if s1.empty?
|
150
|
+
@index = i1
|
151
|
+
r1 = nil
|
152
|
+
else
|
153
|
+
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
|
154
|
+
end
|
155
|
+
s0 << r1
|
156
|
+
if r1
|
157
|
+
r3 = _nt_space
|
158
|
+
s0 << r3
|
159
|
+
if r3
|
160
|
+
r5 = _nt_more_expression
|
161
|
+
if r5
|
162
|
+
r4 = r5
|
163
|
+
else
|
164
|
+
r4 = instantiate_node(SyntaxNode,input, index...index)
|
165
|
+
end
|
166
|
+
s0 << r4
|
167
|
+
end
|
168
|
+
end
|
169
|
+
if s0.last
|
170
|
+
r0 = instantiate_node(ExpressionNode,input, i0...index, s0)
|
171
|
+
r0.extend(Expression0)
|
172
|
+
else
|
173
|
+
@index = i0
|
174
|
+
r0 = nil
|
175
|
+
end
|
176
|
+
|
177
|
+
node_cache[:expression][start_index] = r0
|
178
|
+
|
179
|
+
r0
|
180
|
+
end
|
181
|
+
|
182
|
+
module MoreExpression0
|
183
|
+
def space
|
184
|
+
elements[0]
|
185
|
+
end
|
186
|
+
|
187
|
+
def expression
|
188
|
+
elements[1]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def _nt_more_expression
|
193
|
+
start_index = index
|
194
|
+
if node_cache[:more_expression].has_key?(index)
|
195
|
+
cached = node_cache[:more_expression][index]
|
196
|
+
if cached
|
197
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
198
|
+
@index = cached.interval.end
|
199
|
+
end
|
200
|
+
return cached
|
201
|
+
end
|
202
|
+
|
203
|
+
i0, s0 = index, []
|
204
|
+
r1 = _nt_space
|
205
|
+
s0 << r1
|
206
|
+
if r1
|
207
|
+
r2 = _nt_expression
|
208
|
+
s0 << r2
|
209
|
+
end
|
210
|
+
if s0.last
|
211
|
+
r0 = instantiate_node(ExpressionNode,input, i0...index, s0)
|
212
|
+
r0.extend(MoreExpression0)
|
213
|
+
else
|
214
|
+
@index = i0
|
215
|
+
r0 = nil
|
216
|
+
end
|
217
|
+
|
218
|
+
node_cache[:more_expression][start_index] = r0
|
219
|
+
|
220
|
+
r0
|
221
|
+
end
|
222
|
+
|
223
|
+
module GroupedTerm0
|
224
|
+
end
|
225
|
+
|
226
|
+
def _nt_grouped_term
|
227
|
+
start_index = index
|
228
|
+
if node_cache[:grouped_term].has_key?(index)
|
229
|
+
cached = node_cache[:grouped_term][index]
|
230
|
+
if cached
|
231
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
232
|
+
@index = cached.interval.end
|
233
|
+
end
|
234
|
+
return cached
|
235
|
+
end
|
236
|
+
|
237
|
+
i0, s0 = index, []
|
238
|
+
if has_terminal?('(', false, index)
|
239
|
+
r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
|
240
|
+
@index += 1
|
241
|
+
else
|
242
|
+
terminal_parse_failure('(')
|
243
|
+
r1 = nil
|
244
|
+
end
|
245
|
+
s0 << r1
|
246
|
+
if r1
|
247
|
+
s2, i2 = [], index
|
248
|
+
loop do
|
249
|
+
r3 = _nt_term
|
250
|
+
if r3
|
251
|
+
s2 << r3
|
252
|
+
else
|
253
|
+
break
|
254
|
+
end
|
255
|
+
end
|
256
|
+
if s2.empty?
|
257
|
+
@index = i2
|
258
|
+
r2 = nil
|
259
|
+
else
|
260
|
+
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
|
261
|
+
end
|
262
|
+
s0 << r2
|
263
|
+
if r2
|
264
|
+
if has_terminal?(')', false, index)
|
265
|
+
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
|
266
|
+
@index += 1
|
267
|
+
else
|
268
|
+
terminal_parse_failure(')')
|
269
|
+
r4 = nil
|
270
|
+
end
|
271
|
+
s0 << r4
|
272
|
+
end
|
273
|
+
end
|
274
|
+
if s0.last
|
275
|
+
r0 = instantiate_node(GroupedTermNode,input, i0...index, s0)
|
276
|
+
r0.extend(GroupedTerm0)
|
277
|
+
else
|
278
|
+
@index = i0
|
279
|
+
r0 = nil
|
280
|
+
end
|
281
|
+
|
282
|
+
node_cache[:grouped_term][start_index] = r0
|
283
|
+
|
284
|
+
r0
|
285
|
+
end
|
286
|
+
|
287
|
+
module QuotedTerm0
|
288
|
+
def quoted_stuff
|
289
|
+
elements[1]
|
290
|
+
end
|
291
|
+
|
292
|
+
end
|
293
|
+
|
294
|
+
def _nt_quoted_term
|
295
|
+
start_index = index
|
296
|
+
if node_cache[:quoted_term].has_key?(index)
|
297
|
+
cached = node_cache[:quoted_term][index]
|
298
|
+
if cached
|
299
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
300
|
+
@index = cached.interval.end
|
301
|
+
end
|
302
|
+
return cached
|
303
|
+
end
|
304
|
+
|
305
|
+
i0, s0 = index, []
|
306
|
+
if has_terminal?('"', false, index)
|
307
|
+
r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
|
308
|
+
@index += 1
|
309
|
+
else
|
310
|
+
terminal_parse_failure('"')
|
311
|
+
r1 = nil
|
312
|
+
end
|
313
|
+
s0 << r1
|
314
|
+
if r1
|
315
|
+
s2, i2 = [], index
|
316
|
+
loop do
|
317
|
+
if has_terminal?('\G[^\\"]', true, index)
|
318
|
+
r3 = true
|
319
|
+
@index += 1
|
320
|
+
else
|
321
|
+
r3 = nil
|
322
|
+
end
|
323
|
+
if r3
|
324
|
+
s2 << r3
|
325
|
+
else
|
326
|
+
break
|
327
|
+
end
|
328
|
+
end
|
329
|
+
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
|
330
|
+
s0 << r2
|
331
|
+
if r2
|
332
|
+
if has_terminal?('"', false, index)
|
333
|
+
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
|
334
|
+
@index += 1
|
335
|
+
else
|
336
|
+
terminal_parse_failure('"')
|
337
|
+
r4 = nil
|
338
|
+
end
|
339
|
+
s0 << r4
|
340
|
+
end
|
341
|
+
end
|
342
|
+
if s0.last
|
343
|
+
r0 = instantiate_node(QuotedTermNode,input, i0...index, s0)
|
344
|
+
r0.extend(QuotedTerm0)
|
345
|
+
else
|
346
|
+
@index = i0
|
347
|
+
r0 = nil
|
348
|
+
end
|
349
|
+
|
350
|
+
node_cache[:quoted_term][start_index] = r0
|
351
|
+
|
352
|
+
r0
|
353
|
+
end
|
354
|
+
|
355
|
+
module Term0
|
356
|
+
def space1
|
357
|
+
elements[0]
|
358
|
+
end
|
359
|
+
|
360
|
+
def space2
|
361
|
+
elements[2]
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def _nt_term
|
366
|
+
start_index = index
|
367
|
+
if node_cache[:term].has_key?(index)
|
368
|
+
cached = node_cache[:term][index]
|
369
|
+
if cached
|
370
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
371
|
+
@index = cached.interval.end
|
372
|
+
end
|
373
|
+
return cached
|
374
|
+
end
|
375
|
+
|
376
|
+
i0, s0 = index, []
|
377
|
+
r1 = _nt_space
|
378
|
+
s0 << r1
|
379
|
+
if r1
|
380
|
+
i2 = index
|
381
|
+
r3 = _nt_boolean_term
|
382
|
+
if r3
|
383
|
+
r2 = r3
|
384
|
+
else
|
385
|
+
r4 = _nt_quoted_term
|
386
|
+
if r4
|
387
|
+
r2 = r4
|
388
|
+
else
|
389
|
+
r5 = _nt_word
|
390
|
+
if r5
|
391
|
+
r2 = r5
|
392
|
+
else
|
393
|
+
r6 = _nt_grouped_term
|
394
|
+
if r6
|
395
|
+
r2 = r6
|
396
|
+
else
|
397
|
+
@index = i2
|
398
|
+
r2 = nil
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
s0 << r2
|
404
|
+
if r2
|
405
|
+
r7 = _nt_space
|
406
|
+
s0 << r7
|
407
|
+
end
|
408
|
+
end
|
409
|
+
if s0.last
|
410
|
+
r0 = instantiate_node(TermNode,input, i0...index, s0)
|
411
|
+
r0.extend(Term0)
|
412
|
+
else
|
413
|
+
@index = i0
|
414
|
+
r0 = nil
|
415
|
+
end
|
416
|
+
|
417
|
+
node_cache[:term][start_index] = r0
|
418
|
+
|
419
|
+
r0
|
420
|
+
end
|
421
|
+
|
422
|
+
def _nt_word
|
423
|
+
start_index = index
|
424
|
+
if node_cache[:word].has_key?(index)
|
425
|
+
cached = node_cache[:word][index]
|
426
|
+
if cached
|
427
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
428
|
+
@index = cached.interval.end
|
429
|
+
end
|
430
|
+
return cached
|
431
|
+
end
|
432
|
+
|
433
|
+
s0, i0 = [], index
|
434
|
+
loop do
|
435
|
+
r1 = _nt_word_char
|
436
|
+
if r1
|
437
|
+
s0 << r1
|
438
|
+
else
|
439
|
+
break
|
440
|
+
end
|
441
|
+
end
|
442
|
+
if s0.empty?
|
443
|
+
@index = i0
|
444
|
+
r0 = nil
|
445
|
+
else
|
446
|
+
r0 = instantiate_node(WordNode,input, i0...index, s0)
|
447
|
+
end
|
448
|
+
|
449
|
+
node_cache[:word][start_index] = r0
|
450
|
+
|
451
|
+
r0
|
452
|
+
end
|
453
|
+
|
454
|
+
def _nt_word_char
|
455
|
+
start_index = index
|
456
|
+
if node_cache[:word_char].has_key?(index)
|
457
|
+
cached = node_cache[:word_char][index]
|
458
|
+
if cached
|
459
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
460
|
+
@index = cached.interval.end
|
461
|
+
end
|
462
|
+
return cached
|
463
|
+
end
|
464
|
+
|
465
|
+
i0 = index
|
466
|
+
r1 = _nt_letter
|
467
|
+
if r1
|
468
|
+
r0 = r1
|
469
|
+
else
|
470
|
+
r2 = _nt_digit
|
471
|
+
if r2
|
472
|
+
r0 = r2
|
473
|
+
else
|
474
|
+
if has_terminal?('_', false, index)
|
475
|
+
r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
|
476
|
+
@index += 1
|
477
|
+
else
|
478
|
+
terminal_parse_failure('_')
|
479
|
+
r3 = nil
|
480
|
+
end
|
481
|
+
if r3
|
482
|
+
r0 = r3
|
483
|
+
else
|
484
|
+
if has_terminal?('&', false, index)
|
485
|
+
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
|
486
|
+
@index += 1
|
487
|
+
else
|
488
|
+
terminal_parse_failure('&')
|
489
|
+
r4 = nil
|
490
|
+
end
|
491
|
+
if r4
|
492
|
+
r0 = r4
|
493
|
+
else
|
494
|
+
if has_terminal?('.', false, index)
|
495
|
+
r5 = instantiate_node(SyntaxNode,input, index...(index + 1))
|
496
|
+
@index += 1
|
497
|
+
else
|
498
|
+
terminal_parse_failure('.')
|
499
|
+
r5 = nil
|
500
|
+
end
|
501
|
+
if r5
|
502
|
+
r0 = r5
|
503
|
+
else
|
504
|
+
if has_terminal?('!', false, index)
|
505
|
+
r6 = instantiate_node(SyntaxNode,input, index...(index + 1))
|
506
|
+
@index += 1
|
507
|
+
else
|
508
|
+
terminal_parse_failure('!')
|
509
|
+
r6 = nil
|
510
|
+
end
|
511
|
+
if r6
|
512
|
+
r0 = r6
|
513
|
+
else
|
514
|
+
@index = i0
|
515
|
+
r0 = nil
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
node_cache[:word_char][start_index] = r0
|
524
|
+
|
525
|
+
r0
|
526
|
+
end
|
527
|
+
|
528
|
+
def _nt_letter
|
529
|
+
start_index = index
|
530
|
+
if node_cache[:letter].has_key?(index)
|
531
|
+
cached = node_cache[:letter][index]
|
532
|
+
if cached
|
533
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
534
|
+
@index = cached.interval.end
|
535
|
+
end
|
536
|
+
return cached
|
537
|
+
end
|
538
|
+
|
539
|
+
if has_terminal?('\G[A-Za-z]', true, index)
|
540
|
+
r0 = instantiate_node(SyntaxNode,input, index...(index + 1))
|
541
|
+
@index += 1
|
542
|
+
else
|
543
|
+
r0 = nil
|
544
|
+
end
|
545
|
+
|
546
|
+
node_cache[:letter][start_index] = r0
|
547
|
+
|
548
|
+
r0
|
549
|
+
end
|
550
|
+
|
551
|
+
def _nt_digit
|
552
|
+
start_index = index
|
553
|
+
if node_cache[:digit].has_key?(index)
|
554
|
+
cached = node_cache[:digit][index]
|
555
|
+
if cached
|
556
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
557
|
+
@index = cached.interval.end
|
558
|
+
end
|
559
|
+
return cached
|
560
|
+
end
|
561
|
+
|
562
|
+
if has_terminal?('\G[0-9]', true, index)
|
563
|
+
r0 = instantiate_node(SyntaxNode,input, index...(index + 1))
|
564
|
+
@index += 1
|
565
|
+
else
|
566
|
+
r0 = nil
|
567
|
+
end
|
568
|
+
|
569
|
+
node_cache[:digit][start_index] = r0
|
570
|
+
|
571
|
+
r0
|
572
|
+
end
|
573
|
+
|
574
|
+
def _nt_space
|
575
|
+
start_index = index
|
576
|
+
if node_cache[:space].has_key?(index)
|
577
|
+
cached = node_cache[:space][index]
|
578
|
+
if cached
|
579
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
580
|
+
@index = cached.interval.end
|
581
|
+
end
|
582
|
+
return cached
|
583
|
+
end
|
584
|
+
|
585
|
+
s0, i0 = [], index
|
586
|
+
loop do
|
587
|
+
if has_terminal?('\G[\\s]', true, index)
|
588
|
+
r1 = true
|
589
|
+
@index += 1
|
590
|
+
else
|
591
|
+
r1 = nil
|
592
|
+
end
|
593
|
+
if r1
|
594
|
+
s0 << r1
|
595
|
+
else
|
596
|
+
break
|
597
|
+
end
|
598
|
+
end
|
599
|
+
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
|
600
|
+
|
601
|
+
node_cache[:space][start_index] = r0
|
602
|
+
|
603
|
+
r0
|
604
|
+
end
|
605
|
+
|
606
|
+
def _nt_boolean_term
|
607
|
+
start_index = index
|
608
|
+
if node_cache[:boolean_term].has_key?(index)
|
609
|
+
cached = node_cache[:boolean_term][index]
|
610
|
+
if cached
|
611
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
612
|
+
@index = cached.interval.end
|
613
|
+
end
|
614
|
+
return cached
|
615
|
+
end
|
616
|
+
|
617
|
+
i0 = index
|
618
|
+
r1 = _nt_and_term
|
619
|
+
if r1
|
620
|
+
r0 = r1
|
621
|
+
else
|
622
|
+
r2 = _nt_or_term
|
623
|
+
if r2
|
624
|
+
r0 = r2
|
625
|
+
else
|
626
|
+
@index = i0
|
627
|
+
r0 = nil
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
node_cache[:boolean_term][start_index] = r0
|
632
|
+
|
633
|
+
r0
|
634
|
+
end
|
635
|
+
|
636
|
+
module AndTerm0
|
637
|
+
def term
|
638
|
+
elements[1]
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
def _nt_and_term
|
643
|
+
start_index = index
|
644
|
+
if node_cache[:and_term].has_key?(index)
|
645
|
+
cached = node_cache[:and_term][index]
|
646
|
+
if cached
|
647
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
648
|
+
@index = cached.interval.end
|
649
|
+
end
|
650
|
+
return cached
|
651
|
+
end
|
652
|
+
|
653
|
+
i0, s0 = index, []
|
654
|
+
i1 = index
|
655
|
+
if has_terminal?('and', false, index)
|
656
|
+
r2 = instantiate_node(SyntaxNode,input, index...(index + 3))
|
657
|
+
@index += 3
|
658
|
+
else
|
659
|
+
terminal_parse_failure('and')
|
660
|
+
r2 = nil
|
661
|
+
end
|
662
|
+
if r2
|
663
|
+
r1 = r2
|
664
|
+
else
|
665
|
+
if has_terminal?('&&', false, index)
|
666
|
+
r3 = instantiate_node(SyntaxNode,input, index...(index + 2))
|
667
|
+
@index += 2
|
668
|
+
else
|
669
|
+
terminal_parse_failure('&&')
|
670
|
+
r3 = nil
|
671
|
+
end
|
672
|
+
if r3
|
673
|
+
r1 = r3
|
674
|
+
else
|
675
|
+
@index = i1
|
676
|
+
r1 = nil
|
677
|
+
end
|
678
|
+
end
|
679
|
+
s0 << r1
|
680
|
+
if r1
|
681
|
+
r4 = _nt_term
|
682
|
+
s0 << r4
|
683
|
+
end
|
684
|
+
if s0.last
|
685
|
+
r0 = instantiate_node(AndTermNode,input, i0...index, s0)
|
686
|
+
r0.extend(AndTerm0)
|
687
|
+
else
|
688
|
+
@index = i0
|
689
|
+
r0 = nil
|
690
|
+
end
|
691
|
+
|
692
|
+
node_cache[:and_term][start_index] = r0
|
693
|
+
|
694
|
+
r0
|
695
|
+
end
|
696
|
+
|
697
|
+
module OrTerm0
|
698
|
+
def term
|
699
|
+
elements[1]
|
700
|
+
end
|
701
|
+
end
|
702
|
+
|
703
|
+
def _nt_or_term
|
704
|
+
start_index = index
|
705
|
+
if node_cache[:or_term].has_key?(index)
|
706
|
+
cached = node_cache[:or_term][index]
|
707
|
+
if cached
|
708
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
709
|
+
@index = cached.interval.end
|
710
|
+
end
|
711
|
+
return cached
|
712
|
+
end
|
713
|
+
|
714
|
+
i0, s0 = index, []
|
715
|
+
i1 = index
|
716
|
+
if has_terminal?('or', false, index)
|
717
|
+
r2 = instantiate_node(SyntaxNode,input, index...(index + 2))
|
718
|
+
@index += 2
|
719
|
+
else
|
720
|
+
terminal_parse_failure('or')
|
721
|
+
r2 = nil
|
722
|
+
end
|
723
|
+
if r2
|
724
|
+
r1 = r2
|
725
|
+
else
|
726
|
+
if has_terminal?('||', false, index)
|
727
|
+
r3 = instantiate_node(SyntaxNode,input, index...(index + 2))
|
728
|
+
@index += 2
|
729
|
+
else
|
730
|
+
terminal_parse_failure('||')
|
731
|
+
r3 = nil
|
732
|
+
end
|
733
|
+
if r3
|
734
|
+
r1 = r3
|
735
|
+
else
|
736
|
+
@index = i1
|
737
|
+
r1 = nil
|
738
|
+
end
|
739
|
+
end
|
740
|
+
s0 << r1
|
741
|
+
if r1
|
742
|
+
r4 = _nt_term
|
743
|
+
s0 << r4
|
744
|
+
end
|
745
|
+
if s0.last
|
746
|
+
r0 = instantiate_node(OrTermNode,input, i0...index, s0)
|
747
|
+
r0.extend(OrTerm0)
|
748
|
+
else
|
749
|
+
@index = i0
|
750
|
+
r0 = nil
|
751
|
+
end
|
752
|
+
|
753
|
+
node_cache[:or_term][start_index] = r0
|
754
|
+
|
755
|
+
r0
|
756
|
+
end
|
757
|
+
|
758
|
+
end
|
759
|
+
|
760
|
+
class TQueryParser < Treetop::Runtime::CompiledParser
|
761
|
+
include TQuery
|
762
|
+
end
|