preval 0.3.0 → 0.4.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 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