preval 0.2.0 → 0.3.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: 8e6a0c329c5ed18bfae8f21d08c13d96a2abb5c258d4233795c75c1e40019c3a
4
- data.tar.gz: ea764b40b8983db9e8398a86df971c03d47be73bf3c31ec043293e3f8323cf93
3
+ metadata.gz: 31253267bb39c592ddd4ee513348088c751021d98064f26f17c1a723877eaa45
4
+ data.tar.gz: f3b1607bda33cfd394a4730c1f443b572a2c101e0db73c05c270db3ff148cb96
5
5
  SHA512:
6
- metadata.gz: 14f7fd99735026b877061a53db6c13b795e4ea0a98e9ae3f0bfbd52ccabec28e712027323074c1b564f0c561c1f2af726e550e5415ad9014bfb5e9c42f63deb9
7
- data.tar.gz: 1551ab23dfb06db810a3522727db2ae1915084f00c560072fafbb8033d921a113c78d51edaead97ad19eff82320ee883b81d43b0d870a5e4305949c269402050
6
+ metadata.gz: 86be3184bfbc1cae9aace4b5526c8e4ad6e47fd65c7a717df51b3a7e07c68acd7878474ebebc615c4525b5394dbe05b7039f3a4c6f6f25818ff60afeff12a762
7
+ data.tar.gz: 320346f7ecbd7c75326a2793c0f2751921cdcead050851fe32ca9a0c5b336e873c4e1d27dad121aea32663fb0673d23e377da1e538c3a3baed94d28131bbd33e
data/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ 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
+ ### Added
9
+ - Fold constant for exponentiation if exponent is 0 and value is an integer.
10
+ - Replace `.reverse.each` usage with `.reverse_each`.
11
+ - Replace `foo ... in` loops with `.each do` loops.
12
+ - Replace `.gsub('...', '...')` with `.tr('...', '...')` if the arguments are strings and they are of length 1.
13
+ - Replace `def foo; @foo; end` with `attr_reader :foo`.
14
+ - Replace `.shuffle.first` with `.sample`.
15
+ - Replace `.map { ... }.flatten(1)` with `.flat_map { ... }`.
8
16
 
9
17
  ## [0.2.0] - 2019-04-18
10
18
  ### Added
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- preval (0.2.0)
4
+ preval (0.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -28,11 +28,18 @@ If you're using the `bootsnap` gem, `preval` will automatically hook into its co
28
28
 
29
29
  Each optimization is generally named for the function it performs, and can be enabled through the `enable!` method on the visitor class. If you do not explicitly call `enable!` on any optimizations, nothing will change with your source.
30
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
31
+ * `Preval::Visitors::Arithmetic` replaces:
32
+ * constant expressions with their evaluation (e.g., `5 + 2` becomes `7`)
33
+ * arithmetic identities with their evaluation (e.g., `a * 1` becomes `a`)
34
+ * `Preval::Visitors::Micro` replaces:
35
+ * `def foo; @foo; end` with `attr_reader :foo`
36
+ * `.gsub('...', '...')` with `.tr('...', '...')` if the arguments are strings and are both of length 1
37
+ * `.map { ... }.flatten(1)` with `.flat_map { ... }`
38
+ * `.reverse.each` with `.reverse_each`
39
+ * `.shuffle.first` with `.sample`
40
+ * `Preval::Visitors::Loops` replaces:
41
+ * `for ... in ... end` loops with `... each do ... end` loops
42
+ * `while true ... end` loops with `loop do ... end` loops
36
43
 
37
44
  ## Development
38
45
 
data/bin/console CHANGED
@@ -5,6 +5,7 @@ require 'preval'
5
5
 
6
6
  Preval::Visitors::Arithmetic.enable!
7
7
  Preval::Visitors::Loops.enable!
8
+ Preval::Visitors::Micro.enable!
8
9
 
9
10
  require 'irb'
10
11
  IRB.start(__FILE__)
data/bin/parse ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ripper'
4
+
5
+ source = ARGV.first
6
+ pp Ripper.sexp_raw(File.exist?(source) ? File.read(source) : source)
data/bin/print CHANGED
@@ -5,6 +5,7 @@ require 'preval'
5
5
 
6
6
  Preval::Visitors::Arithmetic.enable!
7
7
  Preval::Visitors::Loops.enable!
8
+ Preval::Visitors::Micro.enable!
8
9
 
9
10
  source = ARGV.first
10
11
  puts Preval.process(File.exist?(source) ? File.read(source) : source)
data/docs/index.html CHANGED
@@ -23,6 +23,7 @@ textarea {
23
23
  background-color: black;
24
24
  box-sizing: border-box;
25
25
  color: white;
26
+ font-family: inherit;
26
27
  resize: none;
27
28
  }
28
29
 
data/docs/server.rb CHANGED
@@ -4,6 +4,10 @@ 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!
10
+
7
11
  get '/' do
8
12
  send_file(File.expand_path('index.html', __dir__))
9
13
  end
data/lib/preval.rb CHANGED
@@ -21,8 +21,10 @@ require 'preval/node'
21
21
  require 'preval/parser'
22
22
  require 'preval/version'
23
23
  require 'preval/visitor'
24
+
24
25
  require 'preval/visitors/arithmetic'
25
26
  require 'preval/visitors/loops'
27
+ require 'preval/visitors/micro'
26
28
 
27
29
  if defined?(Bootsnap)
28
30
  load_iseq = RubyVM::InstructionSequence.method(:load_iseq)
data/lib/preval/format.rb CHANGED
@@ -10,23 +10,23 @@ module Preval
10
10
  to(:aref) { body[1] ? "#{source(0)}[#{source(1)}]" : "#{source(0)}[]" }
11
11
  to(:aref_field) { "#{source(0)}[#{source(1)}]" }
12
12
  to(:arg_paren) { body[0].nil? ? '' : "(#{source(0)})" }
13
- to(:args_add) { starts_with?(:args_new) ? source(1) : join(',') }
13
+ to(:args_add) { starts_with?(:args_new) ? source(1) : join(', ') }
14
14
  to(:args_add_block) do
15
15
  args, block = body
16
16
 
17
17
  parts = args.is?(:args_new) ? [] : [args.to_source]
18
- parts << "#{parts.any? ? ',' : ''}&#{block.to_source}" if block
18
+ parts << "#{parts.any? ? ', ' : ''}&#{block.to_source}" if block
19
19
 
20
20
  parts.join
21
21
  end
22
- to(:args_add_star) { starts_with?(:args_new) ? "*#{source(1)}" : "#{source(0)},*#{source(1)}" }
22
+ to(:args_add_star) { starts_with?(:args_new) ? "*#{source(1)}" : "#{source(0)}, *#{source(1)}" }
23
23
  to(:args_new) { '' }
24
24
  to(:assign) { "#{source(0)} = #{source(1)}" }
25
25
  to(:array) { body[0].nil? ? '[]' : "#{starts_with?(:args_add) ? '[' : ''}#{source(0)}]" }
26
26
  to(:assoc_new) { starts_with?(:@label) ? join(' ') : join(' => ') }
27
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(',') }
28
+ to(:assoclist_from_args) { body[0].map(&:to_source).join(', ') }
29
+ to(:bare_assoc_hash) { body[0].map(&:to_source).join(', ') }
30
30
  to(:begin) { "begin\n#{join("\n")}\nend" }
31
31
  to(:BEGIN) { "BEGIN {\n#{source(0)}\n}"}
32
32
  to(:binary) { "#{source(0)} #{body[1]} #{source(2)}" }
@@ -67,7 +67,7 @@ module Preval
67
67
  to(:ensure) { "ensure\n#{source(0)}" }
68
68
  to(:fcall) { join }
69
69
  to(:field) { join }
70
- to(:for) { "#{source(1)}.each do |#{source(0)}|\n#{source(2)}\nend" }
70
+ to(:for) { "for #{source(0)} in #{source(1)} do\n#{source(2)}\nend" }
71
71
  to(:hash) { body[0].nil? ? '{}' : "{ #{join} }" }
72
72
  to(:if) { "if #{source(0)}\n#{source(1)}\n#{body[2] ? "#{source(2)}\n" : ''}end" }
73
73
  to(:if_mod) { "#{source(1)} if #{source(0)}" }
@@ -78,11 +78,11 @@ module Preval
78
78
  to(:method_add_arg) { body[1].is?(:args_new) ? source(0) : join }
79
79
  to(:method_add_block) { join }
80
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)}" : '*'}" }
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
84
  to(:mlhs_paren) { "(#{source(0)})" }
85
- to(:mrhs_add) { join(',') }
85
+ to(:mrhs_add) { join(', ') }
86
86
  to(:mrhs_add_star) { "*#{join}" }
87
87
  to(:mrhs_new) { '' }
88
88
  to(:mrhs_new_from_args) { source(0) }
@@ -102,7 +102,7 @@ module Preval
102
102
  parts << kwarg_rest.to_source if kwarg_rest
103
103
  parts << block.to_source if block
104
104
 
105
- parts.join(',')
105
+ parts.join(', ')
106
106
  end
107
107
  to(:program) { "#{join("\n")}\n" }
108
108
  to(:qsymbols_add) { join(starts_with?(:qsymbols_new) ? '' : ' ') }
data/lib/preval/node.rb CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  module Preval
4
4
  class Node
5
+ class TypeMatch
6
+ attr_reader :types
7
+
8
+ def initialize(types)
9
+ @types = types
10
+ end
11
+
12
+ def match?(node)
13
+ node.body.size == types.size &&
14
+ node.body.zip(types).all? do |(left, right)|
15
+ left.is_a?(Node) && Array(right).include?(left.type)
16
+ end
17
+ end
18
+
19
+ def self.match?(types, node)
20
+ new(types).match?(node)
21
+ end
22
+ end
23
+
5
24
  include Format
6
25
 
7
26
  attr_reader :type, :body, :literal
@@ -12,6 +31,13 @@ module Preval
12
31
  @literal = literal
13
32
  end
14
33
 
34
+ def dig(index, *args)
35
+ node = body[index]
36
+ return nil unless node
37
+
38
+ args.any? ? node.dig(*args) : node
39
+ end
40
+
15
41
  def join(delim = '')
16
42
  body.map(&:to_source).join(delim)
17
43
  end
@@ -44,6 +70,10 @@ module Preval
44
70
  end
45
71
  end
46
72
 
73
+ def type_match?(*types)
74
+ TypeMatch.new(types).match?(self)
75
+ end
76
+
47
77
  def update(type, body)
48
78
  @type = type
49
79
  @body = body
@@ -1,3 +1,3 @@
1
1
  module Preval
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -38,7 +38,9 @@ module Preval
38
38
  node.replace(right)
39
39
  end
40
40
  elsif operation == :**
41
- if right.int?(1)
41
+ if left.is?(:@int) && right.int?(0)
42
+ node.update(:@int, left.to_int < 0 ? -1 : 1)
43
+ elsif right.int?(1)
42
44
  node.replace(left)
43
45
  elsif left.int?(1)
44
46
  node.replace(left)
@@ -13,11 +13,25 @@ module Preval
13
13
 
14
14
  using TrueNode
15
15
 
16
+ def on_for(node)
17
+ sexp = Parser.parse(<<~CODE)
18
+ #{node.source(1)}.each do |#{node.source(0)}|
19
+ #{node.source(2)}
20
+ end
21
+ CODE
22
+
23
+ node.update(:stmts_add, sexp.body[0].body)
24
+ end
25
+
16
26
  def on_while(node)
17
- predicate, statements = node.body
18
- return unless predicate.true?
27
+ return unless node.body[0].true?
28
+
29
+ sexp = Parser.parse(<<~CODE)
30
+ loop do
31
+ #{node.source(1)}
32
+ end
33
+ CODE
19
34
 
20
- sexp = Parser.parse("loop do\n#{statements.to_source}\nend")
21
35
  node.update(:stmts_add, sexp.body[0].body)
22
36
  end
23
37
  end
@@ -0,0 +1,72 @@
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
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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Deisz
@@ -68,6 +68,7 @@ files:
68
68
  - README.md
69
69
  - Rakefile
70
70
  - bin/console
71
+ - bin/parse
71
72
  - bin/print
72
73
  - bin/setup
73
74
  - docs/index.html
@@ -80,6 +81,7 @@ files:
80
81
  - lib/preval/visitor.rb
81
82
  - lib/preval/visitors/arithmetic.rb
82
83
  - lib/preval/visitors/loops.rb
84
+ - lib/preval/visitors/micro.rb
83
85
  - preval.gemspec
84
86
  homepage: https://github.com/kddeisz/preval
85
87
  licenses: