piggly 1.2.1 → 2.0.0

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.
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