preval 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ cache: bundler
3
+ rvm: 2.6.0
4
+ before_install: gem install bundler -v 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'sinatra'
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
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task default: :test
data/bin/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'preval'
5
+
6
+ Preval::Visitors::Arithmetic.enable!
7
+ Preval::Visitors::Loops.enable!
8
+
9
+ require 'irb'
10
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
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
@@ -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,3 @@
1
+ module Preval
2
+ VERSION = '0.1.0'
3
+ 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: []