preval 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +4 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +33 -0
- data/LICENSE +21 -0
- data/README.md +49 -0
- data/Rakefile +10 -0
- data/bin/console +10 -0
- data/bin/setup +8 -0
- data/docs/index.html +66 -0
- data/docs/server.rb +15 -0
- data/lib/preval.rb +25 -0
- data/lib/preval/format.rb +179 -0
- data/lib/preval/node.rb +66 -0
- data/lib/preval/parser.rb +23 -0
- data/lib/preval/version.rb +3 -0
- data/lib/preval/visitor.rb +18 -0
- data/lib/preval/visitors/arithmetic.rb +50 -0
- data/lib/preval/visitors/loops.rb +25 -0
- data/preval.gemspec +25 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6b1ef70f4fe7a2d5280f11a2466a8543d95fd55a82a7c148b3e9eee720b3ec03
|
4
|
+
data.tar.gz: 8756a3c7ed21d3b9765925f3888b72349bb215a60230c249e86c87fe3efdeae1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7c60862886c355c7aa6412947e0d0fc7530fe74d8e506ffcbdd9d62a50e80b6a48d586dd777968adaeeadba49fc6ab01cb4cccbc358aa0530d56e51fb2bb68b0
|
7
|
+
data.tar.gz: 41bc24f5cf74cf2f06704cf2519ab46753c54a63be181190dca2eaafa33d425caa0b6ed8709ba39ed8b26723aadeb366c69b6896c06e51d3c190c01970c20df2
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
preval (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
minitest (5.11.3)
|
10
|
+
mustermann (1.0.3)
|
11
|
+
rack (2.0.6)
|
12
|
+
rack-protection (2.0.5)
|
13
|
+
rack
|
14
|
+
rake (12.3.2)
|
15
|
+
sinatra (2.0.5)
|
16
|
+
mustermann (~> 1.0)
|
17
|
+
rack (~> 2.0)
|
18
|
+
rack-protection (= 2.0.5)
|
19
|
+
tilt (~> 2.0)
|
20
|
+
tilt (2.0.9)
|
21
|
+
|
22
|
+
PLATFORMS
|
23
|
+
ruby
|
24
|
+
|
25
|
+
DEPENDENCIES
|
26
|
+
bundler (~> 2.0)
|
27
|
+
minitest (~> 5.11)
|
28
|
+
preval!
|
29
|
+
rake (~> 12.3)
|
30
|
+
sinatra
|
31
|
+
|
32
|
+
BUNDLED WITH
|
33
|
+
2.0.1
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 Kevin Deisz
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Preval
|
2
|
+
|
3
|
+
`preval` is a gem that hooks into the Ruby compilation process and runs optimizations before it gets loaded by the virtual machine.
|
4
|
+
|
5
|
+
Certain optimizations that are common in compilers are not immediately possible with Ruby on account of Ruby's flexibility. For example, most compilers will run through a process called [constant folding](https://en.wikipedia.org/wiki/Constant_folding) to eliminate the need to perform extraneous operations at runtime (e.g., `5 + 2` in the source can be replaced with `7`). However, because Ruby allows you to override the `Integer#+` method, it's possible that `5 + 2` would not evaluate to `7`. `preval` assumes that most developers will not override the `Integer#+` method, and performs optimizations under that assumption.
|
6
|
+
|
7
|
+
Users must opt in to each of `preval`'s optimizations, as there's no real way of telling whether or not it is 100% safe for any codebase. The more optimizations are allowed to run, the most time and memory savings later. Users can also define their own optimizations by subclassing the `Preval::Visitor` class and using the existing `Preval::Node` APIs to replace and update the Ruby AST as necessary.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'preval'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install preval
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
At the moment, this gem is a POC and should not be used in production. If you want to experiment with it, you can use the `bootsnap` gem to hook into the compilation process and run `Preval.process` over the source as it comes through. This will eventually be automated.
|
28
|
+
|
29
|
+
Each optimization is generally named for the function it performs, and can be enabled through the `enable!` method on the visitor class.
|
30
|
+
|
31
|
+
* `Preval::Visitors::Arithmetic`
|
32
|
+
* replaces constant expressions with their evaluation (e.g., `5 + 2` becomes `7`)
|
33
|
+
* replaces certain arithmetic identities with their evaluation (e.g., `a * 1` becomes `a`)
|
34
|
+
* `Preval::Visitors::Loops`
|
35
|
+
* replaces `while true ... end` loops with `loop do ... end` loops
|
36
|
+
|
37
|
+
## Development
|
38
|
+
|
39
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
40
|
+
|
41
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
42
|
+
|
43
|
+
## Contributing
|
44
|
+
|
45
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/kddeisz/preval.
|
46
|
+
|
47
|
+
## License
|
48
|
+
|
49
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
data/docs/index.html
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
7
|
+
|
8
|
+
<title>preval</title>
|
9
|
+
<style>
|
10
|
+
html, body, main {
|
11
|
+
font-family: "Courier New";
|
12
|
+
height: 100%;
|
13
|
+
margin: 0;
|
14
|
+
padding: 0;
|
15
|
+
width: 100%;
|
16
|
+
}
|
17
|
+
|
18
|
+
main {
|
19
|
+
display: flex;
|
20
|
+
}
|
21
|
+
|
22
|
+
textarea {
|
23
|
+
background-color: black;
|
24
|
+
box-sizing: border-box;
|
25
|
+
color: white;
|
26
|
+
resize: none;
|
27
|
+
}
|
28
|
+
|
29
|
+
textarea, code {
|
30
|
+
flex-basis: 50%;
|
31
|
+
font-size: 18px;
|
32
|
+
padding: .5em;
|
33
|
+
}
|
34
|
+
</style>
|
35
|
+
</head>
|
36
|
+
<body>
|
37
|
+
<main>
|
38
|
+
<textarea placeholder="Enter Ruby code..."></textarea>
|
39
|
+
<code></code>
|
40
|
+
</main>
|
41
|
+
<script>
|
42
|
+
const textarea = document.querySelector("textarea");
|
43
|
+
const code = document.querySelector("code");
|
44
|
+
|
45
|
+
const fetchCode = () => {
|
46
|
+
var xhr = new XMLHttpRequest();
|
47
|
+
xhr.open("POST", "/", true);
|
48
|
+
|
49
|
+
xhr.onreadystatechange = () => {
|
50
|
+
if (xhr.readyState === XMLHttpRequest.DONE) {
|
51
|
+
code.innerText = xhr.status === 200 ? xhr.responseText : "";
|
52
|
+
}
|
53
|
+
};
|
54
|
+
|
55
|
+
xhr.send(textarea.value);
|
56
|
+
};
|
57
|
+
|
58
|
+
let timeout = 0;
|
59
|
+
|
60
|
+
textarea.addEventListener("input", () => {
|
61
|
+
clearTimeout(timeout);
|
62
|
+
timeout = setTimeout(fetchCode, 300);
|
63
|
+
});
|
64
|
+
</script>
|
65
|
+
</body>
|
66
|
+
</html>
|
data/docs/server.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'preval'
|
5
|
+
require 'sinatra'
|
6
|
+
|
7
|
+
get '/' do
|
8
|
+
send_file(File.expand_path('index.html', __dir__))
|
9
|
+
end
|
10
|
+
|
11
|
+
post '/' do
|
12
|
+
Preval.process(request.body.read).tap do |response|
|
13
|
+
halt 422 unless response
|
14
|
+
end
|
15
|
+
end
|
data/lib/preval.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ripper'
|
4
|
+
|
5
|
+
module Preval
|
6
|
+
SyntaxError = Class.new(SyntaxError)
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_reader :visitors
|
10
|
+
|
11
|
+
def process(source)
|
12
|
+
visitors.inject(source) { |accum, visitor| visitor.process(accum) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
@visitors = []
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'preval/format'
|
20
|
+
require 'preval/node'
|
21
|
+
require 'preval/parser'
|
22
|
+
require 'preval/version'
|
23
|
+
require 'preval/visitor'
|
24
|
+
require 'preval/visitors/arithmetic'
|
25
|
+
require 'preval/visitors/loops'
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Preval
|
4
|
+
module Format
|
5
|
+
def self.to(type, &block)
|
6
|
+
define_method(:"to_#{type}", &block)
|
7
|
+
end
|
8
|
+
|
9
|
+
to(:alias) { "alias #{source(0)} #{source(1)}" }
|
10
|
+
to(:aref) { body[1] ? "#{source(0)}[#{source(1)}]" : "#{source(0)}[]" }
|
11
|
+
to(:aref_field) { "#{source(0)}[#{source(1)}]" }
|
12
|
+
to(:arg_paren) { body[0].nil? ? '' : "(#{source(0)})" }
|
13
|
+
to(:args_add) { starts_with?(:args_new) ? source(1) : join(',') }
|
14
|
+
to(:args_add_block) do
|
15
|
+
args, block = body
|
16
|
+
|
17
|
+
parts = args.is?(:args_new) ? [] : [args.to_source]
|
18
|
+
parts << "#{parts.any? ? ',' : ''}&#{block.to_source}" if block
|
19
|
+
|
20
|
+
parts.join
|
21
|
+
end
|
22
|
+
to(:args_add_star) { starts_with?(:args_new) ? "*#{source(1)}" : "#{source(0)},*#{source(1)}" }
|
23
|
+
to(:args_new) { '' }
|
24
|
+
to(:assign) { "#{source(0)} = #{source(1)}" }
|
25
|
+
to(:array) { body[0].nil? ? '[]' : "#{starts_with?(:args_add) ? '[' : ''}#{source(0)}]" }
|
26
|
+
to(:assoc_new) { starts_with?(:@label) ? join(' ') : join(' => ') }
|
27
|
+
to(:assoc_splat) { "**#{source(0)}" }
|
28
|
+
to(:assoclist_from_args) { body[0].map(&:to_source).join(',') }
|
29
|
+
to(:bare_assoc_hash) { body[0].map(&:to_source).join(',') }
|
30
|
+
to(:begin) { "begin\n#{join("\n")}\nend" }
|
31
|
+
to(:BEGIN) { "BEGIN {\n#{source(0)}\n}"}
|
32
|
+
to(:binary) { "#{source(0)} #{body[1]} #{source(2)}" }
|
33
|
+
to(:blockarg) { "&#{source(0)}" }
|
34
|
+
to(:block_var) { "|#{source(0)}|" }
|
35
|
+
to(:bodystmt) do
|
36
|
+
source(0).tap do |code|
|
37
|
+
code << "\n#{source(1)}" if body[1]
|
38
|
+
|
39
|
+
if body[2]
|
40
|
+
stmts = body[2].is?(:else) ? body[2].body[0] : body[2]
|
41
|
+
code << "\nelse\n#{stmts.to_source}"
|
42
|
+
end
|
43
|
+
|
44
|
+
code << "\n#{source(3)}" if body[3]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
to(:brace_block) { " { #{body[0] ? source(0) : ''}#{source(1)} }" }
|
48
|
+
to(:break) { body[0].is?(:args_new) ? 'break' : "break #{source(0)}" }
|
49
|
+
to(:call) { "#{source(0)}#{body[1].is_a?(Symbol) ? body[1] : source(1)}#{body[2] === :call ? '' : source(2)}" }
|
50
|
+
to(:case) { "case#{body[0] ? " #{source(0)}" : ''}\n#{source(1)}\nend" }
|
51
|
+
to(:class) { "class #{source(0)}#{body[1] ? " < #{source(1)}\n" : ''}#{source(2)}\nend" }
|
52
|
+
to(:command) { join(' ') }
|
53
|
+
to(:command_call) { "#{source(0)}.#{source(2)} #{source(3)}" }
|
54
|
+
to(:const_path_field) { join('::') }
|
55
|
+
to(:const_path_ref) { join('::') }
|
56
|
+
to(:const_ref) { source(0) }
|
57
|
+
to(:def) { "def #{source(0)} #{source(1)}\n#{source(2)}\nend" }
|
58
|
+
to(:defs) { "def #{source(0)}#{source(1)}#{source(2)} #{source(3)}\n#{source(4)}\nend" }
|
59
|
+
to(:defined) { "defined?(#{source(0)})" }
|
60
|
+
to(:do_block) { " do#{body[0] ? " #{source(0)}" : ''}\n#{source(1)}\nend" }
|
61
|
+
to(:dot2) { join('..') }
|
62
|
+
to(:dot3) { join('...') }
|
63
|
+
to(:dyna_symbol) { ":\"#{source(0)}\"" }
|
64
|
+
to(:END) { "END {\n#{source(0)}\n}"}
|
65
|
+
to(:else) { "else\n#{source(0)}" }
|
66
|
+
to(:elsif) { "elsif #{source(0)}\n#{source(1)}#{body[2] ? "\n#{source(2)}" : ''}" }
|
67
|
+
to(:ensure) { "ensure\n#{source(0)}" }
|
68
|
+
to(:fcall) { join }
|
69
|
+
to(:field) { join }
|
70
|
+
to(:for) { "#{source(1)}.each do |#{source(0)}|\n#{source(2)}\nend" }
|
71
|
+
to(:hash) { body[0].nil? ? '{}' : "{ #{join} }" }
|
72
|
+
to(:if) { "if #{source(0)}\n#{source(1)}\n#{body[2] ? "#{source(2)}\n" : ''}end" }
|
73
|
+
to(:if_mod) { "#{source(1)} if #{source(0)}" }
|
74
|
+
to(:ifop) { "#{source(0)} ? #{source(1)} : #{source(2)}"}
|
75
|
+
to(:kwrest_param) { "**#{body[0] ? source(0) : ''}" }
|
76
|
+
to(:lambda) { "->(#{starts_with?(:paren) ? body[0].body[0].to_source : source(0)}) { #{source(1)} }" }
|
77
|
+
to(:massign) { join(' = ') }
|
78
|
+
to(:method_add_arg) { body[1].is?(:args_new) ? source(0) : join }
|
79
|
+
to(:method_add_block) { join }
|
80
|
+
to(:methref) { join('.:') }
|
81
|
+
to(:mlhs_add) { starts_with?(:mlhs_new) ? source(1) : join(',') }
|
82
|
+
to(:mlhs_add_post) { join(',') }
|
83
|
+
to(:mlhs_add_star) { "#{starts_with?(:mlhs_new) ? '' : "#{source(0)},"}#{body[1] ? "*#{source(1)}" : '*'}" }
|
84
|
+
to(:mlhs_paren) { "(#{source(0)})" }
|
85
|
+
to(:mrhs_add) { join(',') }
|
86
|
+
to(:mrhs_add_star) { "*#{join}" }
|
87
|
+
to(:mrhs_new) { '' }
|
88
|
+
to(:mrhs_new_from_args) { source(0) }
|
89
|
+
to(:module) { "module #{source(0)}#{source(1)}\nend" }
|
90
|
+
to(:next) { starts_with?(:args_new) ? 'next' : "next #{source(0)}" }
|
91
|
+
to(:opassign) { join(' ') }
|
92
|
+
to(:paren) { "(#{join})" }
|
93
|
+
to(:params) do
|
94
|
+
reqs, opts, rest, post, kwargs, kwarg_rest, block = body
|
95
|
+
parts = []
|
96
|
+
|
97
|
+
parts += reqs.map(&:to_source) if reqs
|
98
|
+
parts += opts.map { |opt| "#{opt[0]} = #{opt[1]}" } if opts
|
99
|
+
parts << rest.to_source if rest
|
100
|
+
parts += post.map(&:to_source) if post
|
101
|
+
parts += kwargs.map { |(kwarg, value)| value ? "#{kwarg.to_source} #{value.to_source}" : kwarg.to_source } if kwargs
|
102
|
+
parts << kwarg_rest.to_source if kwarg_rest
|
103
|
+
parts << block.to_source if block
|
104
|
+
|
105
|
+
parts.join(',')
|
106
|
+
end
|
107
|
+
to(:program) { "#{join("\n")}\n" }
|
108
|
+
to(:qsymbols_add) { join(starts_with?(:qsymbols_new) ? '' : ' ') }
|
109
|
+
to(:qsymbols_new) { '%i[' }
|
110
|
+
to(:qwords_add) { join(starts_with?(:qwords_new) ? '' : ' ') }
|
111
|
+
to(:qwords_new) { '%w[' }
|
112
|
+
to(:redo) { 'redo' }
|
113
|
+
to(:regexp_add) { join }
|
114
|
+
to(:regexp_new) { '' }
|
115
|
+
to(:regexp_literal) { "%r{#{source(0)}}#{source(1).slice(1)}" }
|
116
|
+
to(:rescue) do
|
117
|
+
'rescue'.dup.tap do |code|
|
118
|
+
if body[0] || body[1]
|
119
|
+
code << (body[0].is_a?(Array) ? " #{body[0][0].to_source}" : " #{source(0)}") if body[0]
|
120
|
+
code << " => #{source(1)}" if body[1]
|
121
|
+
end
|
122
|
+
|
123
|
+
code << "\n#{source(2)}"
|
124
|
+
code << "\n#{source(3)}" if body[3]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
to(:rescue_mod) { join(' rescue ') }
|
128
|
+
to(:rest_param) { body[0] ? "*#{source(0)}" : '*' }
|
129
|
+
to(:retry) { 'retry' }
|
130
|
+
to(:return) { "return #{source(0)}" }
|
131
|
+
to(:return0) { 'return' }
|
132
|
+
to(:sclass) { "class << #{source(0)}\n#{source(1)}\nend" }
|
133
|
+
to(:stmts_add) { starts_with?(:stmts_new) ? source(1) : join("\n") }
|
134
|
+
to(:string_add) { source(0) << source(1) }
|
135
|
+
to(:string_concat) { join(" \\\n") }
|
136
|
+
to(:string_content) { [] }
|
137
|
+
to(:string_dvar) { "\#{#{source(0)}}" }
|
138
|
+
to(:string_embexpr) { "\#{#{source(0)}}" }
|
139
|
+
to(:string_literal) do
|
140
|
+
content =
|
141
|
+
source(0).map! do |part|
|
142
|
+
part.start_with?('#{') ? part : part.gsub(/([^\\])"/) { "#{$1}\\\"" }
|
143
|
+
end
|
144
|
+
|
145
|
+
"\"#{content.join}\""
|
146
|
+
end
|
147
|
+
to(:super) { "super#{starts_with?(:arg_paren) ? '' : ' '}#{source(0)}" }
|
148
|
+
to(:symbol) { ":#{source(0)}" }
|
149
|
+
to(:symbol_literal) { source(0) }
|
150
|
+
to(:symbols_add) { join(starts_with?(:symbols_new) ? '' : ' ') }
|
151
|
+
to(:symbols_new) { '%I[' }
|
152
|
+
to(:top_const_field) { "::#{source(0)}" }
|
153
|
+
to(:top_const_ref) { "::#{source(0)}" }
|
154
|
+
to(:unary) { "#{body[0][0]}#{source(1)}" }
|
155
|
+
to(:undef) { "undef #{body[0][0].to_source}" }
|
156
|
+
to(:unless) { "unless #{source(0)}\n#{source(1)}\n#{body[2] ? "#{source(2)}\n" : ''}end" }
|
157
|
+
to(:unless_mod) { "#{source(1)} unless #{source(0)}" }
|
158
|
+
to(:until) { "until #{source(0)}\n#{source(1)}\nend" }
|
159
|
+
to(:until_mod) { "#{source(1)} until #{source(0)}" }
|
160
|
+
to(:var_alias) { "alias #{source(0)} #{source(1)}" }
|
161
|
+
to(:var_field) { join }
|
162
|
+
to(:var_ref) { source(0) }
|
163
|
+
to(:vcall) { join }
|
164
|
+
to(:void_stmt) { '' }
|
165
|
+
to(:when) { "when #{source(0)}\n#{source(1)}#{body[2] ? "\n#{source(2)}" : ''}" }
|
166
|
+
to(:while) { "while #{source(0)}\n#{source(1)}\nend" }
|
167
|
+
to(:while_mod) { "#{source(1)} while #{source(0)}" }
|
168
|
+
to(:word_add) { join }
|
169
|
+
to(:word_new) { '' }
|
170
|
+
to(:words_add) { join(starts_with?(:words_new) ? '' : ' ') }
|
171
|
+
to(:words_new) { '%W[' }
|
172
|
+
to(:xstring_add) { join }
|
173
|
+
to(:xstring_new) { '' }
|
174
|
+
to(:xstring_literal) { "%x[#{source(0)}]" }
|
175
|
+
to(:yield) { "yield#{starts_with?(:paren) ? '' : ' '}#{join}" }
|
176
|
+
to(:yield0) { 'yield' }
|
177
|
+
to(:zsuper) { 'super' }
|
178
|
+
end
|
179
|
+
end
|
data/lib/preval/node.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Preval
|
4
|
+
class Node
|
5
|
+
include Format
|
6
|
+
|
7
|
+
attr_reader :type, :body, :literal
|
8
|
+
|
9
|
+
def initialize(type, body, literal = false)
|
10
|
+
@type = type
|
11
|
+
@body = body
|
12
|
+
@literal = literal
|
13
|
+
end
|
14
|
+
|
15
|
+
def join(delim = '')
|
16
|
+
body.map(&:to_source).join(delim)
|
17
|
+
end
|
18
|
+
|
19
|
+
def is?(other)
|
20
|
+
type == other
|
21
|
+
end
|
22
|
+
|
23
|
+
def replace(node)
|
24
|
+
@type = node.type
|
25
|
+
@body = node.body
|
26
|
+
@literal = node.literal
|
27
|
+
end
|
28
|
+
|
29
|
+
def source(index)
|
30
|
+
body[index].to_source
|
31
|
+
end
|
32
|
+
|
33
|
+
def starts_with?(type)
|
34
|
+
body[0].is?(type)
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_source
|
38
|
+
return body if literal
|
39
|
+
|
40
|
+
begin
|
41
|
+
public_send(:"to_#{type}")
|
42
|
+
rescue NoMethodError
|
43
|
+
raise NotImplementedError, "#{type} has not yet been implemented"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def update(type, body)
|
48
|
+
@type = type
|
49
|
+
@body = body
|
50
|
+
@literal = type.to_s.start_with?('@')
|
51
|
+
end
|
52
|
+
|
53
|
+
def visit(pass)
|
54
|
+
return if literal
|
55
|
+
|
56
|
+
handler = :"on_#{type}"
|
57
|
+
pass.public_send(handler, self) if pass.respond_to?(handler)
|
58
|
+
|
59
|
+
return unless body.is_a?(Array)
|
60
|
+
|
61
|
+
body.each do |child|
|
62
|
+
child.visit(pass) if child.is_a?(Node)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Preval
|
4
|
+
class Parser < Ripper::SexpBuilder
|
5
|
+
def self.parse(source)
|
6
|
+
new(source).parse
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
SCANNER_EVENTS.each do |event|
|
12
|
+
define_method(:"on_#{event}") do |token|
|
13
|
+
Node.new(:"@#{event}", token, true)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
PARSER_EVENTS.each do |event|
|
18
|
+
define_method(:"on_#{event}") do |*args|
|
19
|
+
Node.new(event, args)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Preval
|
4
|
+
class Visitor
|
5
|
+
def process(source)
|
6
|
+
sexp = Parser.parse(source)
|
7
|
+
sexp.tap { |node| node.visit(self) }.to_source if sexp
|
8
|
+
end
|
9
|
+
|
10
|
+
def process!(source)
|
11
|
+
process(source).tap { |response| raise SyntaxError unless response }
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.enable!
|
15
|
+
Preval.visitors << new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Preval
|
4
|
+
class Visitors
|
5
|
+
class Arithmetic < Visitor
|
6
|
+
module IntNode
|
7
|
+
refine Node do
|
8
|
+
def int?(value)
|
9
|
+
is?(:@int) && to_int == value
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_int
|
13
|
+
body[0].to_i
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
using IntNode
|
19
|
+
|
20
|
+
OPERATORS = %i[+ - * / % **].freeze
|
21
|
+
|
22
|
+
def on_binary(node)
|
23
|
+
left, operation, right = node.body
|
24
|
+
|
25
|
+
if left.is?(:@int) && OPERATORS.include?(operation) && right.is?(:@int)
|
26
|
+
value = left.to_int.public_send(operation, right.to_int).to_s
|
27
|
+
node.update(:@int, value)
|
28
|
+
elsif %i[+ -].include?(operation)
|
29
|
+
if right.int?(0)
|
30
|
+
node.replace(left)
|
31
|
+
elsif left.int?(0)
|
32
|
+
node.replace(right)
|
33
|
+
end
|
34
|
+
elsif %i[* /].include?(operation)
|
35
|
+
if right.int?(1)
|
36
|
+
node.replace(left)
|
37
|
+
elsif left.int?(1)
|
38
|
+
node.replace(right)
|
39
|
+
end
|
40
|
+
elsif operation == :**
|
41
|
+
if right.int?(1)
|
42
|
+
node.replace(left)
|
43
|
+
elsif left.int?(1)
|
44
|
+
node.replace(left)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Preval
|
4
|
+
class Visitors
|
5
|
+
class Loops < Visitor
|
6
|
+
module TrueNode
|
7
|
+
refine Node do
|
8
|
+
def true?
|
9
|
+
is?(:var_ref) && starts_with?(:@kw) && body[0].body == 'true'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
using TrueNode
|
15
|
+
|
16
|
+
def on_while(node)
|
17
|
+
predicate, statements = node.body
|
18
|
+
return unless predicate.true?
|
19
|
+
|
20
|
+
sexp = Parser.parse("loop do\n#{statements.to_source}\nend")
|
21
|
+
node.update(:stmts_add, sexp.body[0].body)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/preval.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'preval/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'preval'
|
7
|
+
spec.version = Preval::VERSION
|
8
|
+
spec.authors = ['Kevin Deisz']
|
9
|
+
spec.email = ['kevin.deisz@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'Automatically optimizes your Ruby code'
|
12
|
+
spec.homepage = 'https://github.com/kddeisz/preval'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.files = Dir.chdir(__dir__) do
|
16
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
end
|
18
|
+
spec.bindir = 'exe'
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
23
|
+
spec.add_development_dependency 'rake', '~> 12.3'
|
24
|
+
spec.add_development_dependency 'minitest', '~> 5.11'
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: preval
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kevin Deisz
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-03-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '12.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '12.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.11'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.11'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- kevin.deisz@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".travis.yml"
|
64
|
+
- Gemfile
|
65
|
+
- Gemfile.lock
|
66
|
+
- LICENSE
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- bin/console
|
70
|
+
- bin/setup
|
71
|
+
- docs/index.html
|
72
|
+
- docs/server.rb
|
73
|
+
- lib/preval.rb
|
74
|
+
- lib/preval/format.rb
|
75
|
+
- lib/preval/node.rb
|
76
|
+
- lib/preval/parser.rb
|
77
|
+
- lib/preval/version.rb
|
78
|
+
- lib/preval/visitor.rb
|
79
|
+
- lib/preval/visitors/arithmetic.rb
|
80
|
+
- lib/preval/visitors/loops.rb
|
81
|
+
- preval.gemspec
|
82
|
+
homepage: https://github.com/kddeisz/preval
|
83
|
+
licenses:
|
84
|
+
- MIT
|
85
|
+
metadata: {}
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubygems_version: 3.0.2
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: Automatically optimizes your Ruby code
|
105
|
+
test_files: []
|