piggly-nsd 2.3.3
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.
- checksums.yaml +7 -0
- data/README.md +170 -0
- data/Rakefile +33 -0
- data/bin/piggly +8 -0
- data/lib/piggly/command/base.rb +148 -0
- data/lib/piggly/command/report.rb +162 -0
- data/lib/piggly/command/trace.rb +90 -0
- data/lib/piggly/command/untrace.rb +78 -0
- data/lib/piggly/command.rb +8 -0
- data/lib/piggly/compiler/cache_dir.rb +119 -0
- data/lib/piggly/compiler/coverage_report.rb +63 -0
- data/lib/piggly/compiler/trace_compiler.rb +117 -0
- data/lib/piggly/compiler.rb +7 -0
- data/lib/piggly/config.rb +80 -0
- data/lib/piggly/dumper/index.rb +121 -0
- data/lib/piggly/dumper/qualified_name.rb +36 -0
- data/lib/piggly/dumper/qualified_type.rb +141 -0
- data/lib/piggly/dumper/reified_procedure.rb +172 -0
- data/lib/piggly/dumper/skeleton_procedure.rb +112 -0
- data/lib/piggly/dumper.rb +9 -0
- data/lib/piggly/installer.rb +137 -0
- data/lib/piggly/parser/grammar.tt +748 -0
- data/lib/piggly/parser/nodes.rb +378 -0
- data/lib/piggly/parser/traversal.rb +50 -0
- data/lib/piggly/parser/treetop_ruby19_patch.rb +21 -0
- data/lib/piggly/parser.rb +69 -0
- data/lib/piggly/profile.rb +108 -0
- data/lib/piggly/reporter/base.rb +106 -0
- data/lib/piggly/reporter/html_dsl.rb +63 -0
- data/lib/piggly/reporter/index.rb +114 -0
- data/lib/piggly/reporter/procedure.rb +129 -0
- data/lib/piggly/reporter/resources/highlight.js +38 -0
- data/lib/piggly/reporter/resources/piggly.css +515 -0
- data/lib/piggly/reporter/resources/sortable.js +493 -0
- data/lib/piggly/reporter.rb +8 -0
- data/lib/piggly/tags.rb +280 -0
- data/lib/piggly/task.rb +215 -0
- data/lib/piggly/util/blankslate.rb +114 -0
- data/lib/piggly/util/cacheable.rb +19 -0
- data/lib/piggly/util/enumerable.rb +44 -0
- data/lib/piggly/util/file.rb +17 -0
- data/lib/piggly/util/process_queue.rb +96 -0
- data/lib/piggly/util/thunk.rb +39 -0
- data/lib/piggly/util.rb +9 -0
- data/lib/piggly/version.rb +15 -0
- data/lib/piggly.rb +20 -0
- data/spec/examples/compiler/cacheable_spec.rb +190 -0
- data/spec/examples/compiler/report_spec.rb +25 -0
- data/spec/examples/compiler/trace_spec.rb +123 -0
- data/spec/examples/config_spec.rb +63 -0
- data/spec/examples/dumper/index_spec.rb +199 -0
- data/spec/examples/dumper/procedure_spec.rb +116 -0
- data/spec/examples/grammar/expression_spec.rb +302 -0
- data/spec/examples/grammar/statements/assignment_spec.rb +70 -0
- data/spec/examples/grammar/statements/declaration_spec.rb +21 -0
- data/spec/examples/grammar/statements/exception_spec.rb +78 -0
- data/spec/examples/grammar/statements/if_spec.rb +191 -0
- data/spec/examples/grammar/statements/loop_spec.rb +41 -0
- data/spec/examples/grammar/statements/sql_spec.rb +71 -0
- data/spec/examples/grammar/tokens/comment_spec.rb +58 -0
- data/spec/examples/grammar/tokens/datatype_spec.rb +58 -0
- data/spec/examples/grammar/tokens/identifier_spec.rb +74 -0
- data/spec/examples/grammar/tokens/keyword_spec.rb +44 -0
- data/spec/examples/grammar/tokens/label_spec.rb +40 -0
- data/spec/examples/grammar/tokens/literal_spec.rb +30 -0
- data/spec/examples/grammar/tokens/lval_spec.rb +50 -0
- data/spec/examples/grammar/tokens/number_spec.rb +34 -0
- data/spec/examples/grammar/tokens/sqlkeywords_spec.rb +45 -0
- data/spec/examples/grammar/tokens/string_spec.rb +54 -0
- data/spec/examples/grammar/tokens/whitespace_spec.rb +40 -0
- data/spec/examples/installer_spec.rb +59 -0
- data/spec/examples/parser/nodes_spec.rb +73 -0
- data/spec/examples/parser/traversal_spec.rb +14 -0
- data/spec/examples/parser_spec.rb +118 -0
- data/spec/examples/profile_spec.rb +153 -0
- data/spec/examples/reporter/html/dsl_spec.rb +0 -0
- data/spec/examples/reporter/html/index_spec.rb +0 -0
- data/spec/examples/reporter/html_spec.rb +1 -0
- data/spec/examples/reporter_spec.rb +0 -0
- data/spec/examples/tags_spec.rb +285 -0
- data/spec/examples/task_spec.rb +0 -0
- data/spec/examples/util/cacheable_spec.rb +41 -0
- data/spec/examples/util/enumerable_spec.rb +64 -0
- data/spec/examples/util/file_spec.rb +40 -0
- data/spec/examples/util/process_queue_spec.rb +16 -0
- data/spec/examples/util/thunk_spec.rb +59 -0
- data/spec/examples/version_spec.rb +0 -0
- data/spec/issues/007_spec.rb +25 -0
- data/spec/issues/008_spec.rb +73 -0
- data/spec/issues/018_spec.rb +25 -0
- data/spec/issues/028_spec.rb +48 -0
- data/spec/issues/032_spec.rb +98 -0
- data/spec/issues/036_spec.rb +41 -0
- data/spec/spec_helper.rb +312 -0
- data/spec/spec_suite.rb +5 -0
- metadata +162 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
NodeClass = Treetop::Runtime::SyntaxNode
|
|
2
|
+
|
|
3
|
+
class NodeClass
|
|
4
|
+
include Piggly::Parser::Traversal
|
|
5
|
+
|
|
6
|
+
# The 'text_value' method can be used to read the parse tree as Treetop
|
|
7
|
+
# originally read it. The 'source_text' method returns redefined value or falls
|
|
8
|
+
# back to original text_value if none was set.
|
|
9
|
+
|
|
10
|
+
attr_accessor :source_text
|
|
11
|
+
|
|
12
|
+
def source_text
|
|
13
|
+
@source_text || text_value
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
#
|
|
17
|
+
# Return a newly created Tag value, but only the tag.id is attached to the tree. The
|
|
18
|
+
# reason that is we maintain the Tags in a separate collection (to avoid a full traversal
|
|
19
|
+
# just to get the list of tags later), and we can retrieve the Tag associated with this
|
|
20
|
+
# node by its tag_id.
|
|
21
|
+
#
|
|
22
|
+
def tag(prefix = nil, id = nil)
|
|
23
|
+
unless defined? @tag_id
|
|
24
|
+
if named?(:body)
|
|
25
|
+
Piggly::Tags::BlockTag.new(prefix, id)
|
|
26
|
+
else
|
|
27
|
+
Piggly::Tags::EvaluationTag.new(prefix, id)
|
|
28
|
+
end.tap{|tag| @tag_id = tag.id }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def tag_id
|
|
33
|
+
@tag_id or raise RuntimeError, "Node is not tagged"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def tagged?
|
|
37
|
+
defined? @tag_id
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# overridden in subclasses
|
|
41
|
+
def expression?; false end
|
|
42
|
+
def branch?; false end
|
|
43
|
+
def block?; false end
|
|
44
|
+
def stub?; false end
|
|
45
|
+
def loop?; false end
|
|
46
|
+
def for?; false end
|
|
47
|
+
def while?; false end
|
|
48
|
+
def style; nil end
|
|
49
|
+
def comment?; false end
|
|
50
|
+
def whitespace?; false end
|
|
51
|
+
def token?; false end
|
|
52
|
+
def string?; false end
|
|
53
|
+
def datatype?; false end
|
|
54
|
+
def identifier?; false end
|
|
55
|
+
def assignment?; false end
|
|
56
|
+
def sql?; false end
|
|
57
|
+
def statement?; false; end
|
|
58
|
+
def if?; false; end
|
|
59
|
+
def else?; false; end
|
|
60
|
+
def label?; false; end
|
|
61
|
+
def keyword?; false; end
|
|
62
|
+
|
|
63
|
+
def indent(method = nil)
|
|
64
|
+
if method and respond_to?(method)
|
|
65
|
+
send(method).text_value[/\n[\t ]*\z/]
|
|
66
|
+
else
|
|
67
|
+
text_value[/\n[\t ]*\z/]
|
|
68
|
+
end || ""
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# True if node is called `label` by the parent node
|
|
72
|
+
def named?(label)
|
|
73
|
+
if p = parent
|
|
74
|
+
p.respond_to?(label) and p.send(label).equal?(self)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
module Piggly
|
|
80
|
+
module Parser
|
|
81
|
+
module Nodes
|
|
82
|
+
|
|
83
|
+
# ...;
|
|
84
|
+
class Statement < NodeClass
|
|
85
|
+
def statement?
|
|
86
|
+
true
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def terminal?
|
|
90
|
+
false
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class Expression < NodeClass
|
|
95
|
+
def expression?
|
|
96
|
+
true
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def tag(prefix = nil, id = nil)
|
|
100
|
+
unless defined? @tag_id
|
|
101
|
+
if named?(:cond)
|
|
102
|
+
if parent.while?
|
|
103
|
+
# This node is the conditional in a WHILE loop
|
|
104
|
+
Tags::ConditionalLoopTag.new(prefix, id)
|
|
105
|
+
elsif parent.loop?
|
|
106
|
+
# This node is the conditional in a loop
|
|
107
|
+
Tags::UnconditionalLoopTag.new(prefix, id)
|
|
108
|
+
elsif parent.branch?
|
|
109
|
+
# This node is a conditional in a branch
|
|
110
|
+
Tags::ConditionalBranchTag.new(prefix, id)
|
|
111
|
+
else
|
|
112
|
+
Tags::EvaluationTag.new(prefix, id)
|
|
113
|
+
end
|
|
114
|
+
else
|
|
115
|
+
Tags::EvaluationTag.new(prefix, id)
|
|
116
|
+
end.tap{|tag| @tag_id = tag.id }
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def terminal?
|
|
121
|
+
false
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# DECLARE declaration BEGIN body END;
|
|
126
|
+
class Block < Statement
|
|
127
|
+
def block?
|
|
128
|
+
true
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Branches with child 'cond' (Expression) will get a ConditionalBranchTag
|
|
133
|
+
class Branch < Statement
|
|
134
|
+
def branch?
|
|
135
|
+
true
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# IF boolean-cond THEN body
|
|
140
|
+
class If < Branch
|
|
141
|
+
def if?
|
|
142
|
+
true
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def terminal?
|
|
146
|
+
false
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# ELSE body END
|
|
151
|
+
class Else < NodeClass
|
|
152
|
+
def else?
|
|
153
|
+
true
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def terminal?
|
|
157
|
+
false
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# EXCEPTION WHEN boolean-cond THEN body
|
|
162
|
+
class Catch < Branch
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# WHEN match-expr THEN body
|
|
166
|
+
class CaseWhen < Branch
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# WHEN boolean-cond THEN body
|
|
170
|
+
class CondWhen < Branch
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# CONTINUE label WHEN boolean-cond;
|
|
174
|
+
class ContinueWhen < Branch
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# EXIT label WHEN boolean-cond;
|
|
178
|
+
class ExitWhen < Branch
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
class UnconditionalBranch < Statement
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# RETURN expr
|
|
185
|
+
class Return < UnconditionalBranch
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# EXIT label
|
|
189
|
+
class Exit < UnconditionalBranch
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# CONTINUE label
|
|
193
|
+
class Continue < UnconditionalBranch
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# RAISE EXCEPTION expr
|
|
197
|
+
class Throw < UnconditionalBranch
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Loops that have a child named :cond (which should be either an Expression
|
|
201
|
+
# or Sql node) will get a LoopCondTag from the #tag method
|
|
202
|
+
class Loop < Statement
|
|
203
|
+
def loop?
|
|
204
|
+
true
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# FOR var IN expr LOOP body END
|
|
209
|
+
class ForLoop < Loop
|
|
210
|
+
def for?
|
|
211
|
+
true
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# FOREACH var IN ARRAY expr LOOP body END
|
|
216
|
+
class ForEachLoop < Loop
|
|
217
|
+
def for?
|
|
218
|
+
true
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# WHILE boolean-cond LOOP body END
|
|
223
|
+
class WhileLoop < Loop
|
|
224
|
+
def while?
|
|
225
|
+
true
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# RAISE NOTICE expr
|
|
231
|
+
class Raise < Statement
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# CASE search-expr WHEN ...
|
|
235
|
+
class Case < Statement
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# CASE WHEN ...
|
|
239
|
+
class Cond < Statement
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# lval := rval
|
|
243
|
+
class Assignment < Statement
|
|
244
|
+
def assignment?
|
|
245
|
+
true
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Lval of assignment (rval is an Expression)
|
|
250
|
+
class Assignable < NodeClass
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
class Sql < Expression
|
|
254
|
+
def style; "tQ"; end
|
|
255
|
+
|
|
256
|
+
def sql?
|
|
257
|
+
true
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def tag(prefix = nil, id = nil)
|
|
261
|
+
unless defined? @tag_id
|
|
262
|
+
if named?(:cond) and parent.for?
|
|
263
|
+
# This node is the conditional in a FOR loop
|
|
264
|
+
Tags::UnconditionalLoopTag.new(prefix, id)
|
|
265
|
+
else
|
|
266
|
+
Tags::EvaluationTag.new(prefix, id)
|
|
267
|
+
end.tap{|tag| @tag_id = tag.id }
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Terminals have no children
|
|
273
|
+
class Terminal < NodeClass
|
|
274
|
+
def initialize(input, interval, elements = nil)
|
|
275
|
+
# Third argument nil prevents children from being assigned
|
|
276
|
+
super(input, interval, nil)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def terminal?
|
|
280
|
+
true
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# This seems like it should be a Token, but it may contain TComment children
|
|
285
|
+
# that should be highlighted differently than the enclosing whitespace
|
|
286
|
+
class TWhitespace < NodeClass
|
|
287
|
+
def terminal?
|
|
288
|
+
false
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def whitespace?
|
|
292
|
+
true
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
class Token < Terminal
|
|
297
|
+
def token?
|
|
298
|
+
true
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
class TKeyword < Token
|
|
303
|
+
def style; "tK"; end
|
|
304
|
+
|
|
305
|
+
def keyword?
|
|
306
|
+
true
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def tag(prefix = nil, id = nil)
|
|
310
|
+
unless defined? @tag_id
|
|
311
|
+
if named?(:cond) and parent.loop?
|
|
312
|
+
Tags::UnconditionalLoopTag.new(prefix, id)
|
|
313
|
+
else
|
|
314
|
+
Tags::EvaluationTag.new(prefix, id)
|
|
315
|
+
end
|
|
316
|
+
end.tap{|tag| @tag_id = tag.id }
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
class TIdentifier < Token
|
|
321
|
+
def style; "tI"; end
|
|
322
|
+
def identifier?
|
|
323
|
+
true
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
class TDatatype < Token
|
|
328
|
+
def style; "tD"; end
|
|
329
|
+
def datatype?
|
|
330
|
+
true
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
class TString < Token
|
|
335
|
+
def style; "tS"; end
|
|
336
|
+
def string?
|
|
337
|
+
true
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
class TDollarQuoteMarker < Token
|
|
342
|
+
def style; "tM"; end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
class TComment < Token
|
|
346
|
+
def style; "tC"; end
|
|
347
|
+
def comment?
|
|
348
|
+
true
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
class TLabel < Token
|
|
353
|
+
def style; "tL"; end
|
|
354
|
+
def label?
|
|
355
|
+
true
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
class TextNode < Terminal
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Stub nodes have no children, or content
|
|
363
|
+
class StubNode < Terminal
|
|
364
|
+
def stub?
|
|
365
|
+
true
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
class NotImplemented < NodeClass
|
|
370
|
+
def parent=(object)
|
|
371
|
+
# this would go in the constructor, but parent is set from outside
|
|
372
|
+
raise Failure, "Grammar does not implement #{object.source_text} at line #{input.line_of(object.interval.first)}"
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Piggly
|
|
2
|
+
module Parser
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# Routines for traversing a tree; assumes base class defines elements
|
|
6
|
+
# as a method that returns a list of child nodes
|
|
7
|
+
#
|
|
8
|
+
module Traversal
|
|
9
|
+
def inject(init) # :yields: NodeClass => init
|
|
10
|
+
if elements
|
|
11
|
+
elements.inject(yield(init, self)) do |state, e|
|
|
12
|
+
e.inject(state){|succ, n| yield(succ, n) }
|
|
13
|
+
end
|
|
14
|
+
else
|
|
15
|
+
yield(init, self)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def count # :yields: NodeClass => boolean
|
|
20
|
+
inject(0){|sum, e| yield(e) ? sum + 1 : sum }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def find # :yields: NodeClass => boolean
|
|
24
|
+
found = false
|
|
25
|
+
catch :done do
|
|
26
|
+
inject(nil) do |_,e|
|
|
27
|
+
if yield(e)
|
|
28
|
+
found = e
|
|
29
|
+
throw :done
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
found
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def select # :yields: NodeClass => boolean
|
|
37
|
+
inject([]){|list,e| yield(e) ? list << e : list }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def flatten # :yields: NodeClass
|
|
41
|
+
if block_given?
|
|
42
|
+
inject([]){|list,e| list << yield(e) }
|
|
43
|
+
else
|
|
44
|
+
inject([]){|list,e| list << e }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# UTF-8 encoding support for Treetop parser
|
|
2
|
+
# This patch ensures that Treetop parsers properly handle UTF-8 encoded strings
|
|
3
|
+
|
|
4
|
+
require 'treetop/runtime'
|
|
5
|
+
|
|
6
|
+
module Treetop
|
|
7
|
+
module Runtime
|
|
8
|
+
# Patch CompiledParser to handle UTF-8 strings properly
|
|
9
|
+
class CompiledParser
|
|
10
|
+
alias_method :original_prepare_to_parse, :prepare_to_parse
|
|
11
|
+
|
|
12
|
+
def prepare_to_parse(input)
|
|
13
|
+
# Ensure input is treated as UTF-8
|
|
14
|
+
if input.respond_to?(:force_encoding) && input.encoding != Encoding::UTF_8
|
|
15
|
+
input = input.dup.force_encoding('UTF-8')
|
|
16
|
+
end
|
|
17
|
+
original_prepare_to_parse(input)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module Piggly
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Pl/pgSQL Parser, returns a tree of NodeClass values (see nodes.rb)
|
|
5
|
+
#
|
|
6
|
+
module Parser
|
|
7
|
+
|
|
8
|
+
autoload :Nodes, "piggly/parser/nodes"
|
|
9
|
+
autoload :Traversal, "piggly/parser/traversal"
|
|
10
|
+
|
|
11
|
+
class Failure < RuntimeError; end
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
# Returns lazy parse tree (only parsed when the value is needed)
|
|
15
|
+
def parse(string)
|
|
16
|
+
Util::Thunk.new do
|
|
17
|
+
p = parser
|
|
18
|
+
|
|
19
|
+
begin
|
|
20
|
+
# Ensure input is UTF-8 encoded
|
|
21
|
+
input = string.dup.force_encoding('UTF-8')
|
|
22
|
+
# Downcase input for case-insensitive parsing
|
|
23
|
+
input = input.downcase
|
|
24
|
+
tree = p.parse(input)
|
|
25
|
+
tree or raise Failure, "#{p.failure_reason}"
|
|
26
|
+
ensure
|
|
27
|
+
# Restore the original string after parsing
|
|
28
|
+
input.replace(string)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def parser_path; "#{File.dirname(__FILE__)}/parser/parser.rb" end
|
|
34
|
+
def grammar_path; "#{File.dirname(__FILE__)}/parser/grammar.tt" end
|
|
35
|
+
def nodes_path; "#{File.dirname(__FILE__)}/parser/nodes.rb" end
|
|
36
|
+
|
|
37
|
+
# Returns treetop parser (recompiled as needed)
|
|
38
|
+
def parser
|
|
39
|
+
return @parser if @parser
|
|
40
|
+
|
|
41
|
+
load_support
|
|
42
|
+
|
|
43
|
+
# @todo: Compare with the version of treetop
|
|
44
|
+
if Util::File.stale?(parser_path, grammar_path)
|
|
45
|
+
# Regenerate the parser when the grammar is updated
|
|
46
|
+
Treetop::Compiler::GrammarCompiler.new.compile(grammar_path, parser_path)
|
|
47
|
+
# Remove existing constant before loading to avoid warnings
|
|
48
|
+
Object.send(:remove_const, :PigglyParser) if Object.const_defined?(:PigglyParser)
|
|
49
|
+
Object.send(:remove_const, :PigglyParserParser) if Object.const_defined?(:PigglyParserParser)
|
|
50
|
+
load parser_path
|
|
51
|
+
@parser = nil # Clear cache when regenerating
|
|
52
|
+
else
|
|
53
|
+
require parser_path unless defined?(::PigglyParser::Parser)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
@parser ||= ::PigglyParser::Parser.new
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def load_support
|
|
62
|
+
require "treetop"
|
|
63
|
+
require "piggly/parser/treetop_ruby19_patch"
|
|
64
|
+
require "piggly/parser/nodes"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module Piggly
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Collection of all Tags
|
|
5
|
+
#
|
|
6
|
+
class Profile
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@by_id = {}
|
|
10
|
+
@by_cache = {}
|
|
11
|
+
@by_procedure = {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Register a procedure and its list of tags
|
|
15
|
+
def add(procedure, tags, cache = nil)
|
|
16
|
+
tags.each{|t| @by_id[t.id] = t }
|
|
17
|
+
@by_cache[cache] = tags unless cache.nil?
|
|
18
|
+
@by_procedure[procedure.oid] = tags
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def [](object)
|
|
22
|
+
case object
|
|
23
|
+
when String
|
|
24
|
+
@by_id[object] or
|
|
25
|
+
raise "No tag with id #{object}"
|
|
26
|
+
when Dumper::ReifiedProcedure,
|
|
27
|
+
Dumper::SkeletonProcedure
|
|
28
|
+
@by_procedure[object.oid] or
|
|
29
|
+
raise "No tags for procedure #{object.signature}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Record the execution of a coverage tag
|
|
34
|
+
def ping(tag_id, value=nil)
|
|
35
|
+
self[tag_id].ping(value)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Summarizes coverage for each type of tag (branch, block, loop)
|
|
39
|
+
# @return [Hash<Symbol, Hash[:count => Integer, :percent => Float]>]
|
|
40
|
+
def summary(procedure = nil)
|
|
41
|
+
tags =
|
|
42
|
+
if procedure
|
|
43
|
+
if @by_procedure.include?(procedure.oid)
|
|
44
|
+
@by_procedure[procedure.oid]
|
|
45
|
+
else
|
|
46
|
+
[]
|
|
47
|
+
end
|
|
48
|
+
else
|
|
49
|
+
@by_id.values
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
grouped = Util::Enumerable.group_by(tags){|x| x.type }
|
|
53
|
+
|
|
54
|
+
summary = Hash.new{|h,k| h[k] = Hash.new }
|
|
55
|
+
grouped.each do |type, ts|
|
|
56
|
+
summary[type][:count] = ts.size
|
|
57
|
+
summary[type][:percent] = Util::Enumerable.sum(ts){|x| x.to_f } / ts.size
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
summary
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Resets each tag's coverage stats
|
|
64
|
+
def clear
|
|
65
|
+
@by_id.values.each{|x| x.clear }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Write coverage stats to the disk cache
|
|
69
|
+
def store
|
|
70
|
+
@by_cache.each{|cache, tags| cache[:tags] = tags }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def empty?(tags)
|
|
74
|
+
tags.all?{|t| t.to_f.zero? }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @return [String]
|
|
78
|
+
def difference(procedure, tags)
|
|
79
|
+
current = Util::Enumerable.group_by(@by_procedure[procedure.oid]){|x| x.type }
|
|
80
|
+
previous = Util::Enumerable.group_by(tags){|x| x.type }
|
|
81
|
+
|
|
82
|
+
current.default = []
|
|
83
|
+
previous.default = []
|
|
84
|
+
|
|
85
|
+
(current.keys | previous.keys).map do |type|
|
|
86
|
+
pct = Util::Enumerable.sum(current[type]){|x| x.to_f } / current[type].size -
|
|
87
|
+
Util::Enumerable.sum(previous[type]){|x| x.to_f } / previous[type].size
|
|
88
|
+
|
|
89
|
+
"#{"%+0.1f" % pct}% #{type}"
|
|
90
|
+
end.join(", ")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Build a notice processor function that records each tag execution
|
|
94
|
+
# @return [Proc]
|
|
95
|
+
def notice_processor(config, stderr = $stderr)
|
|
96
|
+
pattern = /#{config.trace_prefix} (#{Tags::AbstractTag::PATTERN})(?: (.))?/
|
|
97
|
+
|
|
98
|
+
lambda do |message|
|
|
99
|
+
if m = pattern.match(message)
|
|
100
|
+
ping(m.captures[0], m.captures[1])
|
|
101
|
+
else
|
|
102
|
+
stderr.puts("unknown trace: #{message}")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
end
|