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,90 @@
|
|
1
|
+
module Piggly
|
2
|
+
module Command
|
3
|
+
|
4
|
+
#
|
5
|
+
# This command connects to the database, dumps all stored procedures, compiles them
|
6
|
+
# with instrumentation code, and finally installs the instrumented code.
|
7
|
+
#
|
8
|
+
class Trace < Base
|
9
|
+
end
|
10
|
+
|
11
|
+
class << Trace
|
12
|
+
def main(argv)
|
13
|
+
config = configure(argv)
|
14
|
+
connection = connect(config)
|
15
|
+
index = Dumper::Index.new(config)
|
16
|
+
|
17
|
+
dump(connection, index)
|
18
|
+
|
19
|
+
procedures = filter(config, index)
|
20
|
+
|
21
|
+
if procedures.empty?
|
22
|
+
if config.filters.empty?
|
23
|
+
abort "no stored procedures in the cache"
|
24
|
+
else
|
25
|
+
abort "no stored procedures in the cache matched your criteria"
|
26
|
+
end
|
27
|
+
elsif config.dry_run?
|
28
|
+
puts procedures.map{|p| p.signature }
|
29
|
+
exit 0
|
30
|
+
end
|
31
|
+
|
32
|
+
trace(config, procedures)
|
33
|
+
install(Installer.new(config, connection), procedures, Profile.new)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Writes all stored procedures in the database to disk
|
37
|
+
# @return [void]
|
38
|
+
def dump(connection, index)
|
39
|
+
index.update(Dumper::ReifiedProcedure.all(connection))
|
40
|
+
end
|
41
|
+
|
42
|
+
# Compiles all the stored procedures on disk and installs them
|
43
|
+
# @return [void]
|
44
|
+
def trace(config, procedures)
|
45
|
+
puts "compiling #{procedures.size} procedures"
|
46
|
+
|
47
|
+
compiler = Compiler::TraceCompiler.new(config)
|
48
|
+
queue = Util::ProcessQueue.new
|
49
|
+
procedures.each{|p| queue.add { compiler.compile(p) }}
|
50
|
+
|
51
|
+
# Force parser to load before we start forking
|
52
|
+
Parser.parser
|
53
|
+
queue.execute
|
54
|
+
end
|
55
|
+
|
56
|
+
def install(installer, procedures, profile)
|
57
|
+
puts "tracing #{procedures.size} procedures"
|
58
|
+
installer.install(procedures, profile)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Parses command-line options
|
62
|
+
# @return [Config]
|
63
|
+
def configure(argv, config = Config.new)
|
64
|
+
p = OptionParser.new do |o|
|
65
|
+
o.on("-t", "--dry-run", "only print the names of matching procedures", &o_dry_run(config))
|
66
|
+
o.on("-s", "--select PATTERN", "select procedures matching PATTERN", &o_select(config))
|
67
|
+
o.on("-r", "--reject PATTERN", "ignore procedures matching PATTERN", &o_reject(config))
|
68
|
+
o.on("-c", "--cache-root PATH", "local cache directory", &o_cache_root(config))
|
69
|
+
o.on("-o", "--report-root PATH", "report output directory", &o_report_root(config))
|
70
|
+
o.on("-d", "--database PATH", "read database adapter settings from YAML/JSON file", &o_database_yml(config))
|
71
|
+
o.on("-k", "--connection NAME", "use connection adapter NAME", &o_connection_name(config))
|
72
|
+
o.on("-V", "--version", "show version", &o_version(config))
|
73
|
+
o.on("-h", "--help", "show this message") { abort o.to_s }
|
74
|
+
end
|
75
|
+
|
76
|
+
begin
|
77
|
+
p.parse! argv
|
78
|
+
config
|
79
|
+
rescue OptionParser::InvalidOption,
|
80
|
+
OptionParser::InvalidArgument,
|
81
|
+
OptionParser::MissingArgument
|
82
|
+
puts p
|
83
|
+
puts
|
84
|
+
puts $!
|
85
|
+
exit! 1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Piggly
|
2
|
+
module Command
|
3
|
+
|
4
|
+
class Untrace < Base
|
5
|
+
end
|
6
|
+
|
7
|
+
class << Untrace
|
8
|
+
def main(argv)
|
9
|
+
config = configure(argv)
|
10
|
+
index = Dumper::Index.new(config)
|
11
|
+
connection = connect(config)
|
12
|
+
procedures = filter(config, index)
|
13
|
+
|
14
|
+
if procedures.empty?
|
15
|
+
if config.filters.empty?
|
16
|
+
abort "no stored procedures in the cache"
|
17
|
+
else
|
18
|
+
abort "no stored procedures in the cache matched your criteria"
|
19
|
+
end
|
20
|
+
elsif config.dry_run?
|
21
|
+
puts procedures.map{|x| x.signature }
|
22
|
+
exit 0
|
23
|
+
end
|
24
|
+
|
25
|
+
untrace(Installer.new(config, connection), procedures)
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Restores database procedures from file cache
|
30
|
+
#
|
31
|
+
def untrace(installer, procedures)
|
32
|
+
puts "restoring #{procedures.size} procedures"
|
33
|
+
installer.uninstall(procedures)
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Returns a list of Procedure values that satisfy at least one of the given filters
|
38
|
+
#
|
39
|
+
def find_procedures(filters, index)
|
40
|
+
if filters.empty?
|
41
|
+
index.procedures
|
42
|
+
else
|
43
|
+
filters.inject(Set.new){|set, filter| set | index.procedures.select(&filter) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Parses command-line options
|
49
|
+
#
|
50
|
+
def configure(argv, config = Config.new)
|
51
|
+
p = OptionParser.new do |o|
|
52
|
+
o.on("-t", "--dry-run", "only print the names of matching procedures", &o_dry_run(config))
|
53
|
+
o.on("-s", "--select PATTERN", "select procedures matching PATTERN", &o_select(config))
|
54
|
+
o.on("-r", "--reject PATTERN", "ignore procedures matching PATTERN", &o_reject(config))
|
55
|
+
o.on("-c", "--cache-root PATH", "local cache directory", &o_cache_root(config))
|
56
|
+
o.on("-d", "--database PATH", "read 'piggly' database adapter settings from YAML file", &o_database_yml(config))
|
57
|
+
o.on("-k", "--connection NAME", "use connection adapter NAME", &o_connection_name(config))
|
58
|
+
o.on("-V", "--version", "show version", &o_version(config))
|
59
|
+
o.on("-h", "--help", "show this message") { abort o.to_s }
|
60
|
+
end
|
61
|
+
|
62
|
+
begin
|
63
|
+
p.parse! argv
|
64
|
+
config
|
65
|
+
rescue OptionParser::InvalidOption,
|
66
|
+
OptionParser::InvalidArgument,
|
67
|
+
OptionParser::MissingArgument
|
68
|
+
puts p
|
69
|
+
puts
|
70
|
+
puts $!
|
71
|
+
exit! 1
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
data/lib/piggly/compiler.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
module Piggly
|
2
|
+
module Compiler
|
3
|
+
autoload :CacheDir, "piggly/compiler/cache_dir"
|
4
|
+
autoload :TraceCompiler, "piggly/compiler/trace_compiler"
|
5
|
+
autoload :CoverageReport, "piggly/compiler/coverage_report"
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
module Piggly
|
4
|
+
module Compiler
|
5
|
+
|
6
|
+
#
|
7
|
+
# Each cache unit (any group of data that should be expired and created
|
8
|
+
# together) can be broken apart, to prevent unmarshaling a huge block of
|
9
|
+
# data all at once.
|
10
|
+
#
|
11
|
+
# The interface works like a Hash, so the compile method should return a
|
12
|
+
# hash of objects. Each object is writen to a different file (named by the
|
13
|
+
# hash key) within the same directory. String objects are (usually) read
|
14
|
+
# and written directly to disk, while all other objects are (un-)Marshal'd
|
15
|
+
#
|
16
|
+
# Cache invalidation is done by comparing mtime timestamps on the cached
|
17
|
+
# object's file to all the "source" files (ruby libs, input files, etc)
|
18
|
+
# required to regenerate the data.
|
19
|
+
#
|
20
|
+
class CacheDir
|
21
|
+
# Non-printable ASCII char indicates data should be Marshal'd
|
22
|
+
HINT = /[\000-\010\016-\037\177-\300]/
|
23
|
+
|
24
|
+
def initialize(dir)
|
25
|
+
@dir = dir
|
26
|
+
@data = Hash.new do |h, k|
|
27
|
+
path = File.join(@dir, k.to_s)
|
28
|
+
if File.exists?(path)
|
29
|
+
h[k.to_s] = File.open(path, "rb") do |io|
|
30
|
+
# Detect Marshal'd data
|
31
|
+
if io.read(2) !~ HINT
|
32
|
+
io.rewind
|
33
|
+
io.read
|
34
|
+
else
|
35
|
+
io.rewind
|
36
|
+
Marshal.load(io)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Load given key from file system into memory if needed
|
44
|
+
# @return [Object]
|
45
|
+
def [](key)
|
46
|
+
@data[key.to_s]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Writes through to file system
|
50
|
+
# @return [void]
|
51
|
+
def []=(key, value)
|
52
|
+
@data[key.to_s] = value
|
53
|
+
write(key.to_s => value)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Writes through to file system and returns self
|
57
|
+
# @return [CacheDir] self
|
58
|
+
def update(hash)
|
59
|
+
hash.each{|k,v| self[k] = v }
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [void]
|
64
|
+
def delete(key)
|
65
|
+
path = File.join(@dir, key.to_s)
|
66
|
+
File.unlink(path) if File.exists?(path)
|
67
|
+
@data.delete(key)
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [Array<String>]
|
71
|
+
def keys
|
72
|
+
Dir[@dir + "/*"].map{|f| File.basename(f) }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Creates cachedir, destroys its contents, and returns self
|
76
|
+
# @return [CacheDir] self
|
77
|
+
def clear
|
78
|
+
@data.clear
|
79
|
+
|
80
|
+
if File.exists?(@dir)
|
81
|
+
FileUtils.rm(Dir["#{@dir}/*"])
|
82
|
+
FileUtils.touch(@dir)
|
83
|
+
else
|
84
|
+
FileUtils.mkdir(@dir)
|
85
|
+
end
|
86
|
+
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
# Clears entire cache, replaces contents, and returns self
|
91
|
+
# @return [CacheDir] self
|
92
|
+
def replace(hash)
|
93
|
+
clear
|
94
|
+
update(hash)
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
# Serializes each entry to disk
|
100
|
+
# @return [void]
|
101
|
+
def write(hash)
|
102
|
+
FileUtils.mkdir(@dir) unless File.exists?(@dir)
|
103
|
+
FileUtils.touch(@dir) # Update mtime
|
104
|
+
|
105
|
+
hash.each do |key, value|
|
106
|
+
File.open(File.join(@dir, key.to_s), "wb") do |io|
|
107
|
+
# Marshal if the first two bytes contain non-ASCII
|
108
|
+
if value.is_a?(String) and value[0,2] !~ HINT
|
109
|
+
io.write(value)
|
110
|
+
else
|
111
|
+
Marshal.dump(value, io)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Piggly
|
2
|
+
module Compiler
|
3
|
+
|
4
|
+
#
|
5
|
+
# Produces HTML output to report coverage of tagged nodes in the tree
|
6
|
+
#
|
7
|
+
class CoverageReport
|
8
|
+
include Reporter::HtmlDsl
|
9
|
+
|
10
|
+
def initialize(config)
|
11
|
+
@config = config
|
12
|
+
end
|
13
|
+
|
14
|
+
def compile(procedure, profile)
|
15
|
+
trace = Compiler::TraceCompiler.new(@config)
|
16
|
+
|
17
|
+
if trace.stale?(procedure)
|
18
|
+
raise StaleCacheError,
|
19
|
+
"stale cached syntax tree for #{procedure.name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get (copies of) the tagged nodes from the compiled tree
|
23
|
+
data = trace.compile(procedure)
|
24
|
+
|
25
|
+
return :html => traverse(data[:tree], profile),
|
26
|
+
:lines => 1 .. procedure.source(@config).count("\n") + 1
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
# @return [String]
|
32
|
+
def traverse(node, profile, string = "")
|
33
|
+
if node.tagged?
|
34
|
+
tag = profile[node.tag_id]
|
35
|
+
|
36
|
+
if tag.complete?
|
37
|
+
string << %[<span class="#{tag.style}" id="T#{tag.id}">]
|
38
|
+
else
|
39
|
+
string << %[<span class="#{tag.style}" id="T#{tag.id}" title="#{tag.description}">]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
if node.terminal?
|
44
|
+
if style = node.style
|
45
|
+
string << %[<span class="#{style}">#{e(node.text_value)}</span>]
|
46
|
+
else
|
47
|
+
string << e(node.text_value)
|
48
|
+
end
|
49
|
+
else
|
50
|
+
# Non-terminals never write their text_value
|
51
|
+
node.elements.each{|child| traverse(child, profile, string) }
|
52
|
+
end
|
53
|
+
|
54
|
+
if node.tagged?
|
55
|
+
string << %[</span>]
|
56
|
+
end
|
57
|
+
|
58
|
+
string
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Piggly
|
2
|
+
module Compiler
|
3
|
+
|
4
|
+
#
|
5
|
+
# Walks the parse tree, attaching Tag values and rewriting source code to ping them.
|
6
|
+
#
|
7
|
+
class TraceCompiler
|
8
|
+
include Util::Cacheable
|
9
|
+
|
10
|
+
def initialize(config)
|
11
|
+
@config = config
|
12
|
+
end
|
13
|
+
|
14
|
+
# Is the cache_path is older than its source path or the other files?
|
15
|
+
def stale?(procedure)
|
16
|
+
Util::File.stale?(cache_path(procedure.source_path(@config)),
|
17
|
+
procedure.source_path(@config),
|
18
|
+
*self.class.cache_sources)
|
19
|
+
end
|
20
|
+
|
21
|
+
def compile(procedure)
|
22
|
+
cache = CacheDir.new(cache_path(procedure.source_path(@config)))
|
23
|
+
|
24
|
+
if stale?(procedure)
|
25
|
+
$stdout.puts "Compiling #{procedure.name}"
|
26
|
+
tree = Parser.parse(IO.read(procedure.source_path(@config)))
|
27
|
+
tree = tree.force! if tree.respond_to?(:thunk?)
|
28
|
+
|
29
|
+
tags = []
|
30
|
+
code = traverse(tree, procedure.oid, tags)
|
31
|
+
|
32
|
+
cache.replace(:tree => tree, :code => code, :tags => tags)
|
33
|
+
end
|
34
|
+
|
35
|
+
cache
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
# Rewrites the parse tree to call instrumentation helpers, and destructively
|
41
|
+
# updates `tags` by appending the tags of instrumented nodes
|
42
|
+
# @return [String]
|
43
|
+
def traverse(node, oid, tags)
|
44
|
+
if node.terminal? or node.expression?
|
45
|
+
node.source_text
|
46
|
+
else
|
47
|
+
if node.respond_to?(:condStub) and node.respond_to?(:cond)
|
48
|
+
# Preserve opening parenthesis and whitespace before injecting code. This way
|
49
|
+
# IF(test) becomes IF(piggly_cond(TAG, test)) instead of IFpiggly_cond(TAG, (test))
|
50
|
+
pre, cond = node.cond.expr.text_value.match(/\A(\(?[\t\n\r ]*)(.+)\z/m).captures
|
51
|
+
node.cond.source_text = ""
|
52
|
+
|
53
|
+
tags << node.cond.tag(oid)
|
54
|
+
|
55
|
+
node.condStub.source_text = "#{pre}piggly_cond($PIGGLY$#{node.cond.tag_id}$PIGGLY$, (#{cond}))"
|
56
|
+
node.condStub.source_text << traverse(node.cond.tail, oid, tags) # preserve trailing whitespace
|
57
|
+
end
|
58
|
+
|
59
|
+
if node.respond_to?(:bodyStub)
|
60
|
+
if node.respond_to?(:exitStub) and node.respond_to?(:cond)
|
61
|
+
tags << node.body.tag(oid)
|
62
|
+
tags << node.cond.tag(oid)
|
63
|
+
|
64
|
+
# Hack to simulate a loop conditional statement in stmtForLoop and stmtLoop.
|
65
|
+
# signal condition is true when body is executed, and false when exit stub is reached
|
66
|
+
node.bodyStub.source_text = "perform piggly_cond($PIGGLY$#{node.cond.tag_id}$PIGGLY$, true);#{node.indent(:bodySpace)}"
|
67
|
+
node.bodyStub.source_text << "perform piggly_branch($PIGGLY$#{node.body.tag_id}$PIGGLY$);#{node.indent(:bodySpace)}"
|
68
|
+
|
69
|
+
if node.respond_to?(:doneStub)
|
70
|
+
# Signal the end of an iteration was reached
|
71
|
+
node.doneStub.source_text = "#{node.indent(:bodySpace)}perform piggly_signal($PIGGLY$#{node.cond.tag_id}$PIGGLY$, $PIGGLY$@$PIGGLY$);"
|
72
|
+
node.doneStub.source_text << node.body.indent
|
73
|
+
end
|
74
|
+
|
75
|
+
# Signal the loop terminated
|
76
|
+
node.exitStub.source_text = "\n#{node.indent}perform piggly_cond($PIGGLY$#{node.cond.tag_id}$PIGGLY$, false);"
|
77
|
+
elsif node.respond_to?(:body)
|
78
|
+
# Unconditional branches (or blocks)
|
79
|
+
# BEGIN ... END;
|
80
|
+
# ... ELSE ... END;
|
81
|
+
# CONTINUE label;
|
82
|
+
# EXIT label;
|
83
|
+
tags << node.body.tag(oid)
|
84
|
+
node.bodyStub.source_text = "perform piggly_branch($PIGGLY$#{node.body.tag_id}$PIGGLY$);#{node.indent(:bodySpace)}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Traverse children (in which we just injected code)
|
89
|
+
node.elements.map{|e| traverse(e, oid, tags) }.join
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class << TraceCompiler
|
95
|
+
|
96
|
+
# Each of these files' mtimes are used to determine when another file is stale
|
97
|
+
def cache_sources
|
98
|
+
[Parser.grammar_path,
|
99
|
+
Parser.parser_path,
|
100
|
+
Parser.nodes_path]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|