preval 0.2.0 → 0.3.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: 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: