piggly 1.2.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +163 -0
  3. data/Rakefile +29 -15
  4. data/bin/piggly +4 -244
  5. data/lib/piggly.rb +19 -17
  6. data/lib/piggly/command.rb +9 -0
  7. data/lib/piggly/command/base.rb +148 -0
  8. data/lib/piggly/command/report.rb +162 -0
  9. data/lib/piggly/command/test.rb +157 -0
  10. data/lib/piggly/command/trace.rb +90 -0
  11. data/lib/piggly/command/untrace.rb +78 -0
  12. data/lib/piggly/compiler.rb +7 -5
  13. data/lib/piggly/compiler/cache_dir.rb +119 -0
  14. data/lib/piggly/compiler/coverage_report.rb +63 -0
  15. data/lib/piggly/compiler/trace_compiler.rb +105 -0
  16. data/lib/piggly/config.rb +47 -22
  17. data/lib/piggly/dumper.rb +9 -0
  18. data/lib/piggly/dumper/index.rb +121 -0
  19. data/lib/piggly/dumper/qualified_name.rb +36 -0
  20. data/lib/piggly/dumper/qualified_type.rb +81 -0
  21. data/lib/piggly/dumper/reified_procedure.rb +142 -0
  22. data/lib/piggly/dumper/skeleton_procedure.rb +102 -0
  23. data/lib/piggly/installer.rb +84 -42
  24. data/lib/piggly/parser.rb +43 -49
  25. data/lib/piggly/parser/grammar.tt +289 -313
  26. data/lib/piggly/parser/nodes.rb +270 -211
  27. data/lib/piggly/parser/traversal.rb +35 -33
  28. data/lib/piggly/parser/treetop_ruby19_patch.rb +1 -1
  29. data/lib/piggly/profile.rb +81 -60
  30. data/lib/piggly/reporter.rb +5 -18
  31. data/lib/piggly/reporter/base.rb +103 -0
  32. data/lib/piggly/reporter/html_dsl.rb +63 -0
  33. data/lib/piggly/reporter/index.rb +108 -0
  34. data/lib/piggly/reporter/procedure.rb +104 -0
  35. data/lib/piggly/reporter/resources/highlight.js +21 -0
  36. data/lib/piggly/reporter/{piggly.css → resources/piggly.css} +52 -12
  37. data/lib/piggly/reporter/{sortable.js → resources/sortable.js} +0 -0
  38. data/lib/piggly/tags.rb +280 -0
  39. data/lib/piggly/task.rb +191 -40
  40. data/lib/piggly/util.rb +8 -27
  41. data/lib/piggly/util/blankslate.rb +114 -0
  42. data/lib/piggly/util/cacheable.rb +19 -0
  43. data/lib/piggly/util/enumerable.rb +44 -0
  44. data/lib/piggly/util/file.rb +17 -0
  45. data/lib/piggly/util/process_queue.rb +96 -0
  46. data/lib/piggly/util/thunk.rb +39 -0
  47. data/lib/piggly/version.rb +8 -8
  48. data/spec/examples/compiler/cacheable_spec.rb +190 -0
  49. data/spec/examples/compiler/report_spec.rb +25 -0
  50. data/spec/{compiler → examples/compiler}/trace_spec.rb +7 -57
  51. data/spec/examples/config_spec.rb +61 -0
  52. data/spec/examples/dumper/index_spec.rb +197 -0
  53. data/spec/examples/dumper/procedure_spec.rb +116 -0
  54. data/spec/{grammar → examples/grammar}/expression_spec.rb +60 -60
  55. data/spec/{grammar → examples/grammar}/statements/assignment_spec.rb +15 -15
  56. data/spec/examples/grammar/statements/declaration_spec.rb +21 -0
  57. data/spec/{grammar → examples/grammar}/statements/exception_spec.rb +10 -10
  58. data/spec/{grammar → examples/grammar}/statements/if_spec.rb +47 -34
  59. data/spec/{grammar → examples/grammar}/statements/loop_spec.rb +5 -5
  60. data/spec/{grammar → examples/grammar}/statements/sql_spec.rb +11 -11
  61. data/spec/{grammar → examples/grammar}/tokens/comment_spec.rb +11 -11
  62. data/spec/{grammar → examples/grammar}/tokens/datatype_spec.rb +14 -8
  63. data/spec/{grammar → examples/grammar}/tokens/identifier_spec.rb +26 -10
  64. data/spec/{grammar → examples/grammar}/tokens/keyword_spec.rb +5 -5
  65. data/spec/{grammar → examples/grammar}/tokens/label_spec.rb +7 -7
  66. data/spec/{grammar → examples/grammar}/tokens/literal_spec.rb +1 -1
  67. data/spec/examples/grammar/tokens/lval_spec.rb +50 -0
  68. data/spec/{grammar → examples/grammar}/tokens/number_spec.rb +1 -1
  69. data/spec/{grammar → examples/grammar}/tokens/sqlkeywords_spec.rb +1 -1
  70. data/spec/{grammar → examples/grammar}/tokens/string_spec.rb +9 -9
  71. data/spec/{grammar → examples/grammar}/tokens/whitespace_spec.rb +1 -1
  72. data/spec/examples/installer_spec.rb +59 -0
  73. data/spec/examples/parser/nodes_spec.rb +73 -0
  74. data/spec/examples/parser/traversal_spec.rb +14 -0
  75. data/spec/examples/parser_spec.rb +115 -0
  76. data/spec/examples/profile_spec.rb +153 -0
  77. data/spec/{reporter/html_spec.rb → examples/reporter/html/dsl_spec.rb} +0 -0
  78. data/spec/examples/reporter/html/index_spec.rb +0 -0
  79. data/spec/examples/reporter/html_spec.rb +1 -0
  80. data/spec/examples/reporter_spec.rb +0 -0
  81. data/spec/{compiler → examples}/tags_spec.rb +10 -10
  82. data/spec/examples/task_spec.rb +0 -0
  83. data/spec/examples/util/cacheable_spec.rb +41 -0
  84. data/spec/examples/util/enumerable_spec.rb +64 -0
  85. data/spec/examples/util/file_spec.rb +40 -0
  86. data/spec/examples/util/process_queue_spec.rb +16 -0
  87. data/spec/examples/util/thunk_spec.rb +58 -0
  88. data/spec/examples/version_spec.rb +0 -0
  89. data/spec/issues/007_spec.rb +25 -0
  90. data/spec/issues/008_spec.rb +73 -0
  91. data/spec/issues/018_spec.rb +25 -0
  92. data/spec/spec_helper.rb +253 -9
  93. metadata +136 -93
  94. data/README.markdown +0 -116
  95. data/lib/piggly/compiler/cache.rb +0 -151
  96. data/lib/piggly/compiler/pretty.rb +0 -67
  97. data/lib/piggly/compiler/queue.rb +0 -46
  98. data/lib/piggly/compiler/tags.rb +0 -244
  99. data/lib/piggly/compiler/trace.rb +0 -91
  100. data/lib/piggly/filecache.rb +0 -40
  101. data/lib/piggly/parser/parser.rb +0 -11794
  102. data/lib/piggly/reporter/html.rb +0 -207
  103. data/spec/compiler/cache_spec.rb +0 -9
  104. data/spec/compiler/pretty_spec.rb +0 -9
  105. data/spec/compiler/queue_spec.rb +0 -3
  106. data/spec/compiler/rewrite_spec.rb +0 -3
  107. data/spec/config_spec.rb +0 -58
  108. data/spec/filecache_spec.rb +0 -70
  109. data/spec/fixtures/snippets.sql +0 -158
  110. data/spec/grammar/tokens/lval_spec.rb +0 -50
  111. data/spec/parser_spec.rb +0 -8
  112. data/spec/profile_spec.rb +0 -5
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ module Piggly::Util
4
+
5
+ describe File, "cache invalidation" do
6
+ before do
7
+ mtime = Hash['a' => 1, 'b' => 2, 'c' => 3]
8
+ ::File.stub(:mtime).and_return{|f| mtime.fetch(f) }
9
+ ::File.stub(:exists?).and_return{|f| mtime.include?(f) }
10
+ end
11
+
12
+ it "invalidates non-existant cache file" do
13
+ File.stale?('d', 'a').should == true
14
+ File.stale?('d', 'a', 'b').should == true
15
+ end
16
+
17
+ it "performs validation using file mtimes" do
18
+ File.stale?('c', 'b').should_not be_true
19
+ File.stale?('c', 'a').should_not be_true
20
+ File.stale?('c', 'b', 'a').should_not be_true
21
+ File.stale?('c', 'a', 'b').should_not be_true
22
+
23
+ File.stale?('b', 'a').should_not be_true
24
+ File.stale?('b', 'c').should be_true
25
+ File.stale?('b', 'a', 'c').should be_true
26
+ File.stale?('b', 'c', 'a').should be_true
27
+
28
+ File.stale?('a', 'b').should be_true
29
+ File.stale?('a', 'c').should be_true
30
+ File.stale?('a', 'b', 'c').should be_true
31
+ File.stale?('a', 'c', 'b').should be_true
32
+ end
33
+
34
+ it "assumes sources exist" do
35
+ lambda{ File.stale?('a', 'd') }.should raise_error(StandardError)
36
+ lambda{ File.stale?('c', 'a', 'x') }.should raise_error(StandardError)
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ module Piggly
4
+
5
+ describe Util::ProcessQueue do
6
+ context "when empty" do
7
+ end
8
+
9
+ context "when less than @max items pending" do
10
+ end
11
+
12
+ context "when more than @max items pending" do
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ module Piggly
4
+
5
+ describe Util::Thunk do
6
+
7
+ context "not already evaluated" do
8
+ before do
9
+ @work = mock('computation')
10
+ @thunk = Util::Thunk.new { @work.evaluate }
11
+ end
12
+
13
+ it "responds to thunk? without evaluating" do
14
+ @work.should_not_receive(:evaluate)
15
+ @thunk.thunk?.should be_true
16
+ end
17
+
18
+ it "evaluates when force! is explicitly called" do
19
+ @work.should_receive(:evaluate).and_return(@work)
20
+ @thunk.force!.should == @work
21
+ end
22
+
23
+ it "evaluates when some other method is called" do
24
+ @work.should_receive(:evaluate).and_return(@work)
25
+ @work.should_receive(:something).and_return(@work)
26
+ @thunk.something.should == @work
27
+ end
28
+ end
29
+
30
+ context "previously evaluated" do
31
+ before do
32
+ @work = mock('computation')
33
+ @work.stub(:evaluate).and_return(@work)
34
+
35
+ @thunk = Util::Thunk.new { @work.evaluate }
36
+ @thunk.force!
37
+ end
38
+
39
+ it "responds to thunk? without evaluating" do
40
+ @work.should_not_receive(:evaluate)
41
+ @thunk.thunk?.should be_true
42
+ end
43
+
44
+ it "should not re-evaluate when force! is called" do
45
+ @work.should_not_receive(:evaluate)
46
+ @thunk.force!
47
+ end
48
+
49
+ it "should not re-evaluate when some other method is called" do
50
+ @work.should_not_receive(:evaluate)
51
+ @work.should_receive(:something).and_return(@work)
52
+ @thunk.something.should == @work
53
+ end
54
+ end
55
+
56
+ end
57
+
58
+ end
File without changes
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+
3
+ module Piggly
4
+ describe "github issue #7" do
5
+ include GrammarHelper
6
+
7
+ it "can loop over dynamic query results" do
8
+ node = parse(:stmtForLoop, "FOR r IN EXECUTE 'SELECT * FROM pg_user;' LOOP END LOOP;")
9
+ node.should be_statement
10
+
11
+ cond = node.find{|e| e.named?(:cond) }
12
+ cond.source_text.should == "EXECUTE 'SELECT * FROM pg_user;' "
13
+ cond.should be_sql
14
+ end
15
+
16
+ it "can loop over dynamic query results when query contains the word 'LOOP'" do
17
+ node = parse(:stmtForLoop, "FOR r IN EXECUTE 'SELECT * FROM pg_user.LOOP;' LOOP END LOOP;")
18
+ node.should be_statement
19
+
20
+ cond = node.find{|e| e.named?(:cond) }
21
+ cond.source_text.should == "EXECUTE 'SELECT * FROM pg_user.LOOP;' "
22
+ cond.should be_sql
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,73 @@
1
+ require "spec_helper"
2
+
3
+ module Piggly
4
+
5
+ describe "github issue #8" do
6
+ include GrammarHelper
7
+
8
+ context "with declare" do
9
+ it "doesn't require a space before the := symbol" do
10
+ node, rest = parse_some(:stmtDeclare, "declare a text:= 10; begin")
11
+ # node.count{|e| e.assignment? }.should == 1
12
+ rest.should == "begin"
13
+ end
14
+
15
+ it "doesn't require a space after the := symbol" do
16
+ node, rest = parse_some(:stmtDeclare, "declare a text :=10;")
17
+ rest.should == ""
18
+ # node.count{|e| e.assignment? }.should == 1
19
+ end
20
+
21
+ it "doesn't require a space after the := symbol" do
22
+ node, rest = parse_some(:stmtDeclare, "declare a text :=10; begin")
23
+ # node.count{|e| e.assignment? }.should == 1
24
+ rest.should == "begin"
25
+ end
26
+
27
+ it "allows escaped strings" do
28
+ node, rest = parse_some(:stmtDeclare, "declare a text :=E'\\001abc'; begin")
29
+ # node.count{|e| e.assignment? }.should == 1
30
+ rest.should == "begin"
31
+ end
32
+
33
+ it "allows escaped octal characters" do
34
+ node, rest = parse_some(:stmtDeclare, "declare a text :=E'\\001abc'; begin")
35
+ # node.count{|e| e.assignment? }.should == 1
36
+ rest.should == "begin"
37
+ end
38
+ end
39
+
40
+ context "without declare" do
41
+ it "doesn't require a space before the := symbol" do
42
+ node, rest = parse_some(:statement, "a:= 10; begin")
43
+ node.count{|e| e.assignment? }.should == 1
44
+ rest.should == "begin"
45
+ end
46
+
47
+ it "doesn't require a space after the := symbol" do
48
+ node = parse(:statement, "a :=10;")
49
+ node.should be_statement
50
+ node.count{|e| e.assignment? }.should == 1
51
+ end
52
+
53
+ it "doesn't require a space after the := symbol" do
54
+ node, rest = parse_some(:statement, "a :=10; begin")
55
+ node.count{|e| e.assignment? }.should == 1
56
+ rest.should == "begin"
57
+ end
58
+
59
+ it "allows escaped strings" do
60
+ node, rest = parse_some(:statement, "a :=E'\\001abc'; begin")
61
+ node.count{|e| e.assignment? }.should == 1
62
+ rest.should == "begin"
63
+ end
64
+
65
+ it "allows escaped octal characters" do
66
+ node, rest = parse_some(:statement, "a :=E'\\001abc'; begin")
67
+ node.count{|e| e.assignment? }.should == 1
68
+ rest.should == "begin"
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+
3
+ module Piggly
4
+ describe "github issue #18" do
5
+ include GrammarHelper
6
+
7
+ it "can parse the example" do
8
+ body = <<-SQL
9
+ DECLARE
10
+ schema TEXT = 'pg_catalog';
11
+ r RECORD;
12
+ BEGIN
13
+ FOR r IN EXECUTE 'SELECT * FROM ' || quote_ident(schema) || 'pg_user;'
14
+ LOOP
15
+ -- do nothing
16
+ END LOOP;
17
+ END;
18
+ SQL
19
+
20
+ node = parse(:start, body)
21
+ node.count{|e| e.for? }.should == 1
22
+ node.count{|e| e.comment? }.should == 1
23
+ end
24
+ end
25
+ end
@@ -1,25 +1,31 @@
1
- require 'rubygems'
2
- require 'spec'
1
+ begin
2
+ require "spec"
3
+ rescue LoadError
4
+ require "rspec"
5
+ end
3
6
 
4
- require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'piggly'))
7
+ require "ostruct"
8
+ require File.expand_path("#{File.dirname(__FILE__)}/../lib/piggly")
5
9
 
6
10
  #Dir[File.join(File.dirname(__FILE__), 'mocks', '*')].each do |m|
7
11
  # require File.expand_path(m)
8
12
  #end
9
13
 
14
+ # load runtime dependencies
15
+ Piggly::Parser.parser
16
+
10
17
  module Piggly
11
18
  module GrammarHelper
12
19
 
13
20
  COMMENTS = ["abc defghi", "abc -- abc", "quote's", "a 'str'"]
14
21
 
15
- SQLWORDS = %w[select insert update delete drop alter
16
- commit begin rollback set start vacuum]
22
+ SQLWORDS = %w[select insert update delete drop alter commit set start]
17
23
 
18
- KEYWORDS = %w[as := = alias begin by close constant continue
24
+ KEYWORDS = %w[as := = alias begin by constant continue
19
25
  cursor debug declare diagnostics else elsif elseif
20
- end exception execute exit fetch for from get if
21
- in info insert into is log loop move not notice
22
- null open or perform raise rename result_oid return
26
+ end exception execute exit for from get if
27
+ in info insert into is log loop not notice
28
+ null or raise rename result_oid return
23
29
  reverse row_count scroll strict then to type warning
24
30
  when while]
25
31
 
@@ -45,6 +51,244 @@ module Piggly
45
51
  string.replace input
46
52
  end
47
53
  end
54
+
55
+ # mock NodeClass
56
+ class N < OpenStruct
57
+ # Constructs a terminal
58
+ def self.terminal(text, hash = {})
59
+ new({:text_value => text,
60
+ :terminal? => true}.update(hash))
61
+ end
62
+
63
+ def self.keyword(text)
64
+ terminal(text)
65
+ end
66
+
67
+ # Create a tSpace node
68
+ def self.space(text = ' ')
69
+ terminal(text)
70
+ end
71
+
72
+ # Constructs an inline tComment
73
+ def self.inline_comment(text)
74
+ terminal("/* #{text} */",
75
+ :content => text,
76
+ :elements => ['/*', " #{text} ", '*/'])
77
+ end
78
+
79
+ # Constructs a rest-of-the-line comment
80
+ def self.line_comment(text)
81
+ terminal("-- #{text}\n",
82
+ :content => text,
83
+ :elements => ['--', " #{text}", "\n"])
84
+ end
85
+
86
+ # Constructs a stubNode
87
+ def self.stub
88
+ terminal("")
89
+ end
90
+
91
+ # Constructs an expressionUntil-type node
92
+ def self.expr(code)
93
+ Node.new :head => ' ',
94
+ :expr => code,
95
+ :tail => ' ',
96
+ :elements => [:head, :expr, :tail]
97
+ end
98
+
99
+ # Constructs a sequence (of statements usually)
100
+ def self.sequence(*elements)
101
+ Node.new :elements => elements
102
+ end
103
+
104
+ # Constructs BEGIN/END block or ELSE block
105
+ def self.block(declare, statements)
106
+ Node.new :bodySpace => ' ',
107
+ :bodyStub => stub,
108
+ :body => sequence(*statements),
109
+ :elements => [keyword('begin'), :bodySpace, :bodyStub, :body, keyword('end'), terminal(';')]
110
+ end
111
+
112
+ # Construct a CASE with a match expression
113
+ def self.case(expression, *whens)
114
+ Node.new :expr => expr(expression),
115
+ :cases => whens,
116
+ :elements => [keyword('case'), space, :expr, :cases, keyword('end'), space, keyword('case'), terminal(';')]
117
+ end
118
+
119
+ # Construct a CASE with no match expression
120
+ def self.cond(*whens)
121
+ Node.new :cases => whens,
122
+ :elements => [keyword('case'), space, :cases, keyword('end'), space, keyword('case'), terminal(';')]
123
+ end
124
+
125
+ # Constructs a WHEN branch for a CASE with a match expression
126
+ def self.casewhen(pattern, *statements)
127
+ Node.new :condSpace => space,
128
+ :cond => expr(pattern),
129
+ :bodySpace => space,
130
+ :body => sequence(*statements),
131
+ :elements => [keyword('when'), :condSpace, :cond, keyword('then'), :bodySpace, :bodyStub, :body]
132
+ end
133
+
134
+ # Constructs a WHEN branch for a CASE with no match expression
135
+ def self.condwhen(expression, *statements)
136
+ Node.new :condSpace => space,
137
+ :condStub => stub,
138
+ :cond => expr(expression),
139
+ :bodySpace => space,
140
+ :body => sequence(*statements),
141
+ :elements => [keyword('when'), :condSpace, :condStub, :cond, keyword('then'), :bodySpace, :bodyStub, :body]
142
+ end
143
+
144
+ # Constructs an IF or ELSIF
145
+ def self.if(cond, body, other)
146
+ Node.new :condSpace => nil,
147
+ :condStub => stub,
148
+ :cond => expr(cond),
149
+ :bodySpace => ' ',
150
+ :bodyStub => stub,
151
+ :body => body,
152
+ :else => other,
153
+ :elements => [keyword('if'), :condSpace, :condStub, :cond, keyword('then'), :bodySpace, :body, :else, keyword('end'), space, keyword('if'), terminal(';')]
154
+ end
155
+
156
+ # Constructs an unconditional LOOP
157
+ def self.loop(*statements)
158
+ Node.new :bodySpace => space,
159
+ :bodyStub => stub,
160
+ :body => sequence(*statements),
161
+ :doneStub => stub,
162
+ :elements => [keyword('loop'), :bodySpace, :bodyStub, :body, :doneStub, keyword('end'), space, keyword('loop'), terminal(';'), :exitStub]
163
+ end
164
+
165
+ # Constructs a WHILE loop
166
+ def self.while(condition, *statements)
167
+ Node.new :condSpace => space,
168
+ :condStub => stub,
169
+ :cond => expr(condition),
170
+ :bodySpace => space,
171
+ :bodyStub => stub,
172
+ :body => sequence(*statements),
173
+ :exitStub => stub,
174
+ :elements => [keyword('while'), :condSpace, :condStub, :cond, keyword('loop'), :bodySpace, :bodyStub, :body, keyword('end'), space, keyword('loop'), terminal(';')]
175
+ end
176
+
177
+ # Constructs a FOR loop
178
+ def self.for(idents, iterator, *statements)
179
+ Node.new :condSpace => space,
180
+ :cond => expr(iterator),
181
+ :bodySpace => space,
182
+ :bodyStub => stub,
183
+ :body => sequence(*statements),
184
+ :doneStub => stub,
185
+ :exitStub => stub,
186
+ :elements => [keyword('for'), space, idents, keyword('in'), :condSpace, :cond, keyword('loop'), :bodySpace, :bodyStub, :body, :doneStub, keyword('end'), space, keyword('loop'), terminal(';'), :exitStub]
187
+ end
188
+
189
+ # Constructs an EXIT statement
190
+ def self.exit(condition = nil)
191
+ if condition
192
+ Node.new :body => keyword('exit'),
193
+ :condSpace => space,
194
+ :condStub => stub,
195
+ :cond => expr(condition),
196
+ :elements => [:body, space, keyword('when'), :condSpace, :condStub, :cond, terminal(';')]
197
+ else
198
+ Node.new :bodyStub => stub,
199
+ :body => keyword('exit'),
200
+ :elements => [:bodyStub, :body, ';']
201
+ end
202
+ end
203
+
204
+ # Constructs a CONTINUE statement
205
+ def self.continue(condition = nil)
206
+ if condition
207
+ Node.new :body => keyword('continue'),
208
+ :condSpace => space,
209
+ :condStub => stub,
210
+ :cond => expr(condition),
211
+ :elements => [:body, space, keyword('when'), :condSpace, :condStub, :cond, terminal(';')]
212
+ else
213
+ Node.new :bodyStub => stub,
214
+ :body => keyword('exit'),
215
+ :elements => [:bodyStub, :body, ';']
216
+ end
217
+ end
218
+
219
+ # Constructs a RETURN statement
220
+ def self.return(value)
221
+ Node.new :bodyStub => stubNode,
222
+ :body => sequence(keyword('return'), expr(value)),
223
+ :elements => [:bodyStub, :body]
224
+ end
225
+
226
+ # Constructs a RAISE statement
227
+ def self.raise(level, message)
228
+ if level == 'exception'
229
+ Node.new :bodyStub => stubNode,
230
+ :body => sequence(keyword('raise'), space, keyword('exception'), space, expr(message), terminal(';')),
231
+ :elements => [:bodyStub, :body]
232
+ else
233
+ Node.new :elements => [keyword('raise'), space, keyword(level), space, expr(message), terminal(';')]
234
+ end
235
+ end
236
+
237
+ class Node < N
238
+ def initialize(hash)
239
+ if elements = hash[:elements]
240
+ elements.map!{|e| e.is_a?(Symbol) ? hash.fetch(e) : e }
241
+ end
242
+
243
+ super(hash)
244
+ end
245
+
246
+ def text_value
247
+ @text_value ||= elements.inject('') do |string, e|
248
+ string << e.text_value
249
+ end
250
+ end
251
+ end
252
+
253
+ def initialize(hash)
254
+ super({:parent => nil,
255
+ :terminal? => false,
256
+ :expression? => false,
257
+ :branch? => false,
258
+ :block? => false,
259
+ :stub? => false,
260
+ :loop? => false,
261
+ :for? => false,
262
+ :while? => false,
263
+ :style => nil,
264
+ :sourceText => nil }.update(hash))
265
+
266
+ elements.each{|c| c.parent = self } if elements
267
+ end
268
+
269
+ # Recursively compute the byte-offsets within the source text
270
+ def interval(start=0)
271
+ @start ||= start
272
+ @stop ||= if terminal?
273
+ @start + text_value.size
274
+ else
275
+ # recursively compute intervals
276
+ elements.inject(@start) do |prev, e|
277
+ e.interval(prev).end
278
+ end
279
+ end
280
+ @start...@stop
281
+ end
282
+
283
+ def source_text
284
+ @source_text || text_value
285
+ end
286
+
287
+ def tap
288
+ yield self
289
+ self
290
+ end
291
+ end
48
292
  end
49
293
 
50
294
  module Enumerable