preval 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 31253267bb39c592ddd4ee513348088c751021d98064f26f17c1a723877eaa45
4
- data.tar.gz: f3b1607bda33cfd394a4730c1f443b572a2c101e0db73c05c270db3ff148cb96
3
+ metadata.gz: 9e8e8b1cc9579dafac71315c8138e9a07ece0a40e8c0e0ccdb3f9345574c4446
4
+ data.tar.gz: 5bc5ccf467619084b2243f082466881e1f491ecb844cd622ff612b9c7242ae42
5
5
  SHA512:
6
- metadata.gz: 86be3184bfbc1cae9aace4b5526c8e4ad6e47fd65c7a717df51b3a7e07c68acd7878474ebebc615c4525b5394dbe05b7039f3a4c6f6f25818ff60afeff12a762
7
- data.tar.gz: 320346f7ecbd7c75326a2793c0f2751921cdcead050851fe32ca9a0c5b336e873c4e1d27dad121aea32663fb0673d23e377da1e538c3a3baed94d28131bbd33e
6
+ metadata.gz: aebce20a3d9332bf8e8eb20ea9f71789531b1462398713a22f9fb027c860aaee0d060bb94ddc6f400ceb1a6a94e0619638c54bedadcfa6eaea4a5cf3254cbb32
7
+ data.tar.gz: 515570a0d77a859529530b9113a8309b974c9bf09c7117afc2eee605406725155d3f35995bcac674cd0d3577e4da00891faa189925a573dde08fdd3b2fc9cda6
data/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
+
9
+ ## [0.4.0] - 2019-04-19
10
+ ### Added
11
+ - Replace `def foo=(value); @foo = value; end` with `attr_writer :foo`
12
+ - Replace `while false ... end` loops with nothing
13
+ - Replace `until false ... end` loops with `loop do ... end` loops
14
+ - Replace `until true ... end` loops with nothing
15
+
16
+ ### Changed
17
+ - Extracted out the `Preval::Visitors::AttrAccessor` visitor.
18
+ - Renamed the `Preval::Visitors::Micro` visitor to `Preval::Visitors::Fasterer`.
19
+
20
+ ## [0.3.0] - 2019-04-19
8
21
  ### Added
9
22
  - Fold constant for exponentiation if exponent is 0 and value is an integer.
10
23
  - Replace `.reverse.each` usage with `.reverse_each`.
@@ -13,6 +26,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
13
26
  - Replace `def foo; @foo; end` with `attr_reader :foo`.
14
27
  - Replace `.shuffle.first` with `.sample`.
15
28
  - Replace `.map { ... }.flatten(1)` with `.flat_map { ... }`.
29
+ - Replace `def foo=(value); @foo = value; end` with `attr_writer :foo`.
16
30
 
17
31
  ## [0.2.0] - 2019-04-18
18
32
  ### Added
@@ -22,6 +36,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
22
36
  ### Added
23
37
  - Initial release. 🎉
24
38
 
25
- [Unreleased]: https://github.com/kddeisz/preval/compare/v0.2.0...HEAD
39
+ [Unreleased]: https://github.com/kddeisz/preval/compare/v0.4.0...HEAD
40
+ [0.4.0]: https://github.com/kddeisz/preval/compare/v0.3.0...v0.4.0
41
+ [0.3.0]: https://github.com/kddeisz/preval/compare/v0.2.0...v0.3.0
26
42
  [0.2.0]: https://github.com/kddeisz/preval/compare/v0.1.0...v0.2.0
27
43
  [0.1.0]: https://github.com/kddeisz/preval/compare/49c899...v0.1.0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- preval (0.3.0)
4
+ preval (0.4.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -31,8 +31,10 @@ Each optimization is generally named for the function it performs, and can be en
31
31
  * `Preval::Visitors::Arithmetic` replaces:
32
32
  * constant expressions with their evaluation (e.g., `5 + 2` becomes `7`)
33
33
  * arithmetic identities with their evaluation (e.g., `a * 1` becomes `a`)
34
- * `Preval::Visitors::Micro` replaces:
34
+ * `Preval::Visitors::AttrAccessor` replaces:
35
35
  * `def foo; @foo; end` with `attr_reader :foo`
36
+ * `def foo=(value); @foo = value; end` with `attr_writer :foo`
37
+ * `Preval::Visitors::Fasterer` replaces:
36
38
  * `.gsub('...', '...')` with `.tr('...', '...')` if the arguments are strings and are both of length 1
37
39
  * `.map { ... }.flatten(1)` with `.flat_map { ... }`
38
40
  * `.reverse.each` with `.reverse_each`
@@ -40,6 +42,11 @@ Each optimization is generally named for the function it performs, and can be en
40
42
  * `Preval::Visitors::Loops` replaces:
41
43
  * `for ... in ... end` loops with `... each do ... end` loops
42
44
  * `while true ... end` loops with `loop do ... end` loops
45
+ * `while false ... end` loops with nothing
46
+ * `until false ... end` loops with `loop do ... end` loops
47
+ * `until true ... end` loops with nothing
48
+
49
+ You can also call `Preval.enable_all!` which will enable every built-in visitor. Be especially careful when doing this.
43
50
 
44
51
  ## Development
45
52
 
data/bin/console CHANGED
@@ -3,9 +3,7 @@
3
3
  require 'bundler/setup'
4
4
  require 'preval'
5
5
 
6
- Preval::Visitors::Arithmetic.enable!
7
- Preval::Visitors::Loops.enable!
8
- Preval::Visitors::Micro.enable!
6
+ Preval.enable_all!
9
7
 
10
8
  require 'irb'
11
9
  IRB.start(__FILE__)
data/bin/print CHANGED
@@ -3,9 +3,7 @@
3
3
  require 'bundler/setup'
4
4
  require 'preval'
5
5
 
6
- Preval::Visitors::Arithmetic.enable!
7
- Preval::Visitors::Loops.enable!
8
- Preval::Visitors::Micro.enable!
6
+ Preval.enable_all!
9
7
 
10
8
  source = ARGV.first
11
9
  puts Preval.process(File.exist?(source) ? File.read(source) : source)
data/bin/start ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+
3
+ bundle exec ruby docs/server.rb
data/docs/index.html CHANGED
@@ -1,11 +1,13 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
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">
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
7
 
8
8
  <title>preval</title>
9
+ <link href="/favicon.png" rel="icon" type="image/png" />
10
+
9
11
  <style>
10
12
  html, body, main {
11
13
  font-family: "Courier New";
Binary file
data/docs/server.rb CHANGED
@@ -4,9 +4,7 @@ require 'bundler/setup'
4
4
  require 'preval'
5
5
  require 'sinatra'
6
6
 
7
- Preval::Visitors::Arithmetic.enable!
8
- Preval::Visitors::Loops.enable!
9
- Preval::Visitors::Micro.enable!
7
+ Preval.enable_all!
10
8
 
11
9
  get '/' do
12
10
  send_file(File.expand_path('index.html', __dir__))
data/lib/preval.rb CHANGED
@@ -8,8 +8,17 @@ module Preval
8
8
  class << self
9
9
  attr_reader :visitors
10
10
 
11
+ def enable_all!
12
+ Visitors::Arithmetic.enable!
13
+ Visitors::AttrAccessor.enable!
14
+ Visitors::Fasterer.enable!
15
+ Visitors::Loops.enable!
16
+ end
17
+
11
18
  def process(source)
12
- visitors.inject(source) { |accum, visitor| visitor.process(accum) }
19
+ visitors.inject(Parser.parse(source)) do |current, visitor|
20
+ current.tap { |ast| ast.visit(visitor) }
21
+ end.to_source
13
22
  end
14
23
  end
15
24
 
@@ -23,8 +32,9 @@ require 'preval/version'
23
32
  require 'preval/visitor'
24
33
 
25
34
  require 'preval/visitors/arithmetic'
35
+ require 'preval/visitors/attr_accessor'
36
+ require 'preval/visitors/fasterer'
26
37
  require 'preval/visitors/loops'
27
- require 'preval/visitors/micro'
28
38
 
29
39
  if defined?(Bootsnap)
30
40
  load_iseq = RubyVM::InstructionSequence.method(:load_iseq)
data/lib/preval/node.rb CHANGED
@@ -31,11 +31,11 @@ module Preval
31
31
  @literal = literal
32
32
  end
33
33
 
34
- def dig(index, *args)
34
+ def [](index, *args)
35
35
  node = body[index]
36
36
  return nil unless node
37
37
 
38
- args.any? ? node.dig(*args) : node
38
+ args.any? ? node[*args] : node
39
39
  end
40
40
 
41
41
  def join(delim = '')
@@ -1,3 +1,3 @@
1
1
  module Preval
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -2,15 +2,6 @@
2
2
 
3
3
  module Preval
4
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
5
  def self.enable!
15
6
  Preval.visitors << new
16
7
  end
@@ -3,19 +3,19 @@
3
3
  module Preval
4
4
  class Visitors
5
5
  class Arithmetic < Visitor
6
- module IntNode
7
- refine Node do
8
- def int?(value)
9
- is?(:@int) && to_int == value
10
- end
6
+ using(
7
+ Module.new do
8
+ refine Node do
9
+ def int?(value)
10
+ is?(:@int) && to_int == value
11
+ end
11
12
 
12
- def to_int
13
- body[0].to_i
13
+ def to_int
14
+ body[0].to_i
15
+ end
14
16
  end
15
17
  end
16
- end
17
-
18
- using IntNode
18
+ )
19
19
 
20
20
  OPERATORS = %i[+ - * / % **].freeze
21
21
 
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Preval
4
+ class Visitors
5
+ class AttrAccessor < Visitor
6
+ def on_def(node)
7
+ # auto create attr_readers
8
+ if node.type_match?(:@ident, :params, :bodystmt) &&
9
+ # def foo; @foo; end
10
+ node[1].body.none? &&
11
+ # there are no params to this method
12
+ node[2, 0].type_match?(:stmts_new, :var_ref) &&
13
+ # there is only one statement in the body and its a var reference
14
+ node[2, 0, 1, 0].is?(:@ivar) &&
15
+ # the var reference is referencing an instance variable
16
+ node[0].body == node[2, 0, 1, 0].body[1..-1]
17
+ # the name of the variable matches the name of the method
18
+
19
+ ast = Parser.parse("attr_reader :#{node[0].body}")
20
+ node.update(:stmts_add, ast.body[0].body)
21
+ end
22
+
23
+ # auto create attr_writers
24
+ if node.type_match?(:@ident, :paren, :bodystmt) &&
25
+ # def foo=(value); @foo = value; end
26
+ node[0].body.end_with?('=') &&
27
+ # this is a setter method
28
+ node[1, 0, 0].length == 1 &&
29
+ # there is exactly one required param
30
+ node[1, 0].body[1..-1].none? &&
31
+ # there are no other params
32
+ node[2, 0, 0, 0]&.is?(:stmts_new) &&
33
+ # there is only one statement in the body
34
+ node[2, 0, 1].is?(:assign) &&
35
+ # the only statement is an assignment
36
+ node[2, 0, 1].type_match?(:var_field, :var_ref) &&
37
+ # assigning a variable
38
+ node[2, 0, 1, 0, 0].is?(:@ivar) &&
39
+ # assigning to an instance variable
40
+ node[0].body[0..-2] == node[2, 0, 1, 0, 0].body[1..-1] &&
41
+ # variable name matches the method name
42
+ node[1, 0, 0][0].body == node[2, 0, 1, 1, 0].body
43
+ # assignment variable matches the argument name
44
+
45
+ ast = Parser.parse("attr_writer :#{node[0].body[0..-2]}")
46
+ node.update(:stmts_add, ast.body[0].body)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Preval
4
+ class Visitors
5
+ # All of these optimizations come from the `fasterer` gem.
6
+ class Fasterer < Visitor
7
+ def on_call(node)
8
+ left, _period, right = node.body
9
+
10
+ # replace `.reverse.each` with `.reverse_each`
11
+ # replace `.shuffle.first` with `.sample`
12
+ if node.type_match?(:call, :@period, :@ident) &&
13
+ # foo.each
14
+ left.type_match?(%i[array vcall], :@period, :@ident)
15
+ # foo.reverse
16
+
17
+ callleft, callperiod, callright = left.body
18
+
19
+ if callright.body == 'reverse' && right.body == 'each'
20
+ callright.update(:@ident, 'reverse_each')
21
+ node.update(:call, [callleft, callperiod, callright])
22
+ elsif callright.body == 'shuffle' && right.body == 'first'
23
+ callright.update(:@ident, 'sample')
24
+ node.update(:call, [callleft, callperiod, callright])
25
+ end
26
+ end
27
+ end
28
+
29
+ def on_method_add_arg(node)
30
+ # replace `.gsub('...', '...')` with `.tr('...', '...')`
31
+ if node.type_match?(:call, :arg_paren) &&
32
+ # foo.gsub()
33
+ node[0].type_match?(:vcall, :@period, :@ident) &&
34
+ # foo.gsub
35
+ node[0, 2].body == 'gsub'
36
+ # the method being called is gsub
37
+
38
+ left = node[1, 0, 0, 0, 1]
39
+ right = node[1, 0, 0, 1]
40
+
41
+ if left.is?(:string_literal) &&
42
+ right.is?(:string_literal) &&
43
+ [left, right].all? do |node|
44
+ node[0, 1].is?(:@tstring_content) &&
45
+ node[0, 1].body.length == 1
46
+ end
47
+
48
+ node[0, 2].update(:@ident, 'tr')
49
+ end
50
+ end
51
+
52
+ # replace `.map { ... }.flatten(1)` with `.flat_map { ... }`
53
+ if node.type_match?(:call, :arg_paren) &&
54
+ # foo.flatten()
55
+ node[0].type_match?(:method_add_block, :@period, :@ident) &&
56
+ # foo.map {}
57
+ node[0, 0, 0].type_match?(%i[array vcall], :@period, :@ident) &&
58
+ # foo.flatten
59
+ node[0, 0, 0, 2].body == 'map' &&
60
+ # the inner call is a call to map
61
+ node[0, 2].body == 'flatten' &&
62
+ # the outer call is a call to flatten
63
+ node[1].is?(:arg_paren) &&
64
+ # flatten has a param
65
+ node[1, 0, 0].type_match?(:args_new, :@int) &&
66
+ # there is only one argument to flatten and it is an integer
67
+ node[1, 0, 0, 1].body == '1'
68
+ # the value of the argument to flatten is 1
69
+
70
+ node[0, 0, 0, 2].update(:@ident, 'flat_map')
71
+ node.update(:method_add_block, node[0, 0].body)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -3,36 +3,74 @@
3
3
  module Preval
4
4
  class Visitors
5
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
6
  def on_for(node)
17
- sexp = Parser.parse(<<~CODE)
7
+ ast = Parser.parse(<<~CODE)
18
8
  #{node.source(1)}.each do |#{node.source(0)}|
19
9
  #{node.source(2)}
20
10
  end
21
11
  CODE
22
12
 
23
- node.update(:stmts_add, sexp.body[0].body)
13
+ node.update(:stmts_add, ast[0].body)
24
14
  end
25
15
 
26
16
  def on_while(node)
27
- return unless node.body[0].true?
17
+ # auto replace `while true` with `loop do`
18
+ if node[0].is?(:var_ref) &&
19
+ # the predicate to the while is a variable reference
20
+ node[0, 0].is?(:@kw) &&
21
+ # the variable reference is a keyword
22
+ node[0, 0].body == 'true'
23
+ # the keyword is "true"
28
24
 
29
- sexp = Parser.parse(<<~CODE)
30
- loop do
31
- #{node.source(1)}
32
- end
33
- CODE
25
+ ast = Parser.parse(<<~CODE)
26
+ loop do
27
+ #{node.source(1)}
28
+ end
29
+ CODE
30
+
31
+ node.update(:stmts_add, ast[0].body)
32
+ end
33
+
34
+ # ignore `while false`
35
+ if node[0].is?(:var_ref) &&
36
+ # the predicate to the while is a variable reference
37
+ node[0, 0].is?(:@kw) &&
38
+ # the variable reference is a keyword
39
+ node[0, 0].body == 'false'
40
+ # the kwyword is "false"
34
41
 
35
- node.update(:stmts_add, sexp.body[0].body)
42
+ node.update(:void_stmt, [])
43
+ end
44
+ end
45
+
46
+ def on_until(node)
47
+ # auto replace `until false` with `loop do`
48
+ if node[0].is?(:var_ref) &&
49
+ # the predicate to the while is a variable reference
50
+ node[0, 0].is?(:@kw) &&
51
+ # the variable reference is a keyword
52
+ node[0, 0].body == 'false'
53
+ # the keyword is "false"
54
+
55
+ ast = Parser.parse(<<~CODE)
56
+ loop do
57
+ #{node.source(1)}
58
+ end
59
+ CODE
60
+
61
+ node.update(:stmts_add, ast[0].body)
62
+ end
63
+
64
+ # ignore `until true`
65
+ if node[0].is?(:var_ref) &&
66
+ # the predicate to the until is a variable reference
67
+ node[0, 0].is?(:@kw) &&
68
+ # the variable reference is a keyword
69
+ node[0, 0].body == 'true'
70
+ # the kwyword is "true"
71
+
72
+ node.update(:void_stmt, [])
73
+ end
36
74
  end
37
75
  end
38
76
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: preval
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Deisz
@@ -71,7 +71,9 @@ files:
71
71
  - bin/parse
72
72
  - bin/print
73
73
  - bin/setup
74
+ - bin/start
74
75
  - docs/index.html
76
+ - docs/public/favicon.png
75
77
  - docs/server.rb
76
78
  - lib/preval.rb
77
79
  - lib/preval/format.rb
@@ -80,8 +82,9 @@ files:
80
82
  - lib/preval/version.rb
81
83
  - lib/preval/visitor.rb
82
84
  - lib/preval/visitors/arithmetic.rb
85
+ - lib/preval/visitors/attr_accessor.rb
86
+ - lib/preval/visitors/fasterer.rb
83
87
  - lib/preval/visitors/loops.rb
84
- - lib/preval/visitors/micro.rb
85
88
  - preval.gemspec
86
89
  homepage: https://github.com/kddeisz/preval
87
90
  licenses:
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Preval
4
- class Visitors
5
- class Micro < Visitor
6
- def on_call(node)
7
- left, _period, right = node.body
8
-
9
- if node.type_match?(:call, :@period, :@ident) && left.type_match?(%i[array vcall], :@period, :@ident)
10
- callleft, callperiod, callright = left.body
11
-
12
- if callright.body == 'reverse' && right.body == 'each'
13
- callright.update(:@ident, 'reverse_each')
14
- node.update(:call, [callleft, callperiod, callright])
15
- elsif callright.body == 'shuffle' && right.body == 'first'
16
- callright.update(:@ident, 'sample')
17
- node.update(:call, [callleft, callperiod, callright])
18
- end
19
- end
20
- end
21
-
22
- def on_def(node)
23
- if node.type_match?(:@ident, :params, :bodystmt) &&
24
- node.body[1].body.none? &&
25
- node.dig(2, 0, 0).is?(:stmts_new) &&
26
-
27
- var_ref = node.dig(2, 0, 1)
28
-
29
- if var_ref.is?(:var_ref) &&
30
- var_ref.type_match?(:@ivar) &&
31
- node.body[0].body == var_ref.body[0].body[1..-1]
32
-
33
- sexp = Parser.parse("attr_reader :#{node.body[0].body}")
34
- node.update(:stmts_add, sexp.body[0].body)
35
- end
36
- end
37
- end
38
-
39
- def on_method_add_arg(node)
40
- if node.type_match?(:call, :arg_paren)
41
- if node.body[0].type_match?(:vcall, :@period, :@ident) &&
42
- node.dig(0, 2).body == 'gsub'
43
-
44
- left = node.dig(1, 0, 0, 0, 1)
45
- right = node.dig(1, 0, 0, 1)
46
-
47
- if left.is?(:string_literal) &&
48
- right.is?(:string_literal) &&
49
- [left, right].all? do |node|
50
- node.dig(0, 1).is?(:@tstring_content) &&
51
- node.dig(0, 1).body.length == 1
52
- end
53
-
54
- node.dig(0, 2).update(:@ident, 'tr')
55
- end
56
- elsif node.dig(0).type_match?(:method_add_block, :@period, :@ident) &&
57
- node.dig(0, 0, 0).type_match?(%i[array vcall], :@period, :@ident) &&
58
- node.dig(0, 0, 0, 2).body == 'map' &&
59
- node.dig(0, 2).body == 'flatten' &&
60
- node.dig(1).is?(:arg_paren) &&
61
- node.dig(1, 0, 0, 0).is?(:args_new) &&
62
- node.dig(1, 0, 0, 1).is?(:@int) &&
63
- node.dig(1, 0, 0, 1).body == '1'
64
-
65
- node.dig(0, 0, 0, 2).update(:@ident, 'flat_map')
66
- node.update(:method_add_block, node.dig(0, 0).body)
67
- end
68
- end
69
- end
70
- end
71
- end
72
- end