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.
- checksums.yaml +7 -0
- data/README.md +163 -0
- data/Rakefile +29 -15
- data/bin/piggly +4 -244
- data/lib/piggly.rb +19 -17
- data/lib/piggly/command.rb +9 -0
- data/lib/piggly/command/base.rb +148 -0
- data/lib/piggly/command/report.rb +162 -0
- data/lib/piggly/command/test.rb +157 -0
- data/lib/piggly/command/trace.rb +90 -0
- data/lib/piggly/command/untrace.rb +78 -0
- data/lib/piggly/compiler.rb +7 -5
- 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 +105 -0
- data/lib/piggly/config.rb +47 -22
- data/lib/piggly/dumper.rb +9 -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 +81 -0
- data/lib/piggly/dumper/reified_procedure.rb +142 -0
- data/lib/piggly/dumper/skeleton_procedure.rb +102 -0
- data/lib/piggly/installer.rb +84 -42
- data/lib/piggly/parser.rb +43 -49
- data/lib/piggly/parser/grammar.tt +289 -313
- data/lib/piggly/parser/nodes.rb +270 -211
- data/lib/piggly/parser/traversal.rb +35 -33
- data/lib/piggly/parser/treetop_ruby19_patch.rb +1 -1
- data/lib/piggly/profile.rb +81 -60
- data/lib/piggly/reporter.rb +5 -18
- data/lib/piggly/reporter/base.rb +103 -0
- data/lib/piggly/reporter/html_dsl.rb +63 -0
- data/lib/piggly/reporter/index.rb +108 -0
- data/lib/piggly/reporter/procedure.rb +104 -0
- data/lib/piggly/reporter/resources/highlight.js +21 -0
- data/lib/piggly/reporter/{piggly.css → resources/piggly.css} +52 -12
- data/lib/piggly/reporter/{sortable.js → resources/sortable.js} +0 -0
- data/lib/piggly/tags.rb +280 -0
- data/lib/piggly/task.rb +191 -40
- data/lib/piggly/util.rb +8 -27
- 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/version.rb +8 -8
- data/spec/examples/compiler/cacheable_spec.rb +190 -0
- data/spec/examples/compiler/report_spec.rb +25 -0
- data/spec/{compiler → examples/compiler}/trace_spec.rb +7 -57
- data/spec/examples/config_spec.rb +61 -0
- data/spec/examples/dumper/index_spec.rb +197 -0
- data/spec/examples/dumper/procedure_spec.rb +116 -0
- data/spec/{grammar → examples/grammar}/expression_spec.rb +60 -60
- data/spec/{grammar → examples/grammar}/statements/assignment_spec.rb +15 -15
- data/spec/examples/grammar/statements/declaration_spec.rb +21 -0
- data/spec/{grammar → examples/grammar}/statements/exception_spec.rb +10 -10
- data/spec/{grammar → examples/grammar}/statements/if_spec.rb +47 -34
- data/spec/{grammar → examples/grammar}/statements/loop_spec.rb +5 -5
- data/spec/{grammar → examples/grammar}/statements/sql_spec.rb +11 -11
- data/spec/{grammar → examples/grammar}/tokens/comment_spec.rb +11 -11
- data/spec/{grammar → examples/grammar}/tokens/datatype_spec.rb +14 -8
- data/spec/{grammar → examples/grammar}/tokens/identifier_spec.rb +26 -10
- data/spec/{grammar → examples/grammar}/tokens/keyword_spec.rb +5 -5
- data/spec/{grammar → examples/grammar}/tokens/label_spec.rb +7 -7
- data/spec/{grammar → examples/grammar}/tokens/literal_spec.rb +1 -1
- data/spec/examples/grammar/tokens/lval_spec.rb +50 -0
- data/spec/{grammar → examples/grammar}/tokens/number_spec.rb +1 -1
- data/spec/{grammar → examples/grammar}/tokens/sqlkeywords_spec.rb +1 -1
- data/spec/{grammar → examples/grammar}/tokens/string_spec.rb +9 -9
- data/spec/{grammar → examples/grammar}/tokens/whitespace_spec.rb +1 -1
- 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 +115 -0
- data/spec/examples/profile_spec.rb +153 -0
- data/spec/{reporter/html_spec.rb → 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/{compiler → examples}/tags_spec.rb +10 -10
- 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 +58 -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/spec_helper.rb +253 -9
- metadata +136 -93
- data/README.markdown +0 -116
- data/lib/piggly/compiler/cache.rb +0 -151
- data/lib/piggly/compiler/pretty.rb +0 -67
- data/lib/piggly/compiler/queue.rb +0 -46
- data/lib/piggly/compiler/tags.rb +0 -244
- data/lib/piggly/compiler/trace.rb +0 -91
- data/lib/piggly/filecache.rb +0 -40
- data/lib/piggly/parser/parser.rb +0 -11794
- data/lib/piggly/reporter/html.rb +0 -207
- data/spec/compiler/cache_spec.rb +0 -9
- data/spec/compiler/pretty_spec.rb +0 -9
- data/spec/compiler/queue_spec.rb +0 -3
- data/spec/compiler/rewrite_spec.rb +0 -3
- data/spec/config_spec.rb +0 -58
- data/spec/filecache_spec.rb +0 -70
- data/spec/fixtures/snippets.sql +0 -158
- data/spec/grammar/tokens/lval_spec.rb +0 -50
- data/spec/parser_spec.rb +0 -8
- 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,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
|
data/spec/spec_helper.rb
CHANGED
@@ -1,25 +1,31 @@
|
|
1
|
-
|
2
|
-
require
|
1
|
+
begin
|
2
|
+
require "spec"
|
3
|
+
rescue LoadError
|
4
|
+
require "rspec"
|
5
|
+
end
|
3
6
|
|
4
|
-
require
|
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
|
24
|
+
KEYWORDS = %w[as := = alias begin by constant continue
|
19
25
|
cursor debug declare diagnostics else elsif elseif
|
20
|
-
end exception execute exit
|
21
|
-
in info insert into is log loop
|
22
|
-
null
|
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
|