ripper2ruby 0.0.1

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.
Files changed (111) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.markdown +10 -0
  3. data/lib/core_ext/array/flush.rb +5 -0
  4. data/lib/core_ext/hash/delete_at.rb +5 -0
  5. data/lib/core_ext/object/meta_class.rb +5 -0
  6. data/lib/core_ext/object/try.rb +12 -0
  7. data/lib/erb/stripper.rb +48 -0
  8. data/lib/highlighters/ansi.rb +29 -0
  9. data/lib/ripper/event_log.rb +45 -0
  10. data/lib/ripper/ruby_builder.rb +168 -0
  11. data/lib/ripper/ruby_builder/buffer.rb +34 -0
  12. data/lib/ripper/ruby_builder/events/args.rb +40 -0
  13. data/lib/ripper/ruby_builder/events/array.rb +71 -0
  14. data/lib/ripper/ruby_builder/events/assignment.rb +55 -0
  15. data/lib/ripper/ruby_builder/events/block.rb +80 -0
  16. data/lib/ripper/ruby_builder/events/call.rb +123 -0
  17. data/lib/ripper/ruby_builder/events/case.rb +17 -0
  18. data/lib/ripper/ruby_builder/events/const.rb +47 -0
  19. data/lib/ripper/ruby_builder/events/for.rb +13 -0
  20. data/lib/ripper/ruby_builder/events/hash.rb +24 -0
  21. data/lib/ripper/ruby_builder/events/identifier.rb +41 -0
  22. data/lib/ripper/ruby_builder/events/if.rb +37 -0
  23. data/lib/ripper/ruby_builder/events/lexer.rb +159 -0
  24. data/lib/ripper/ruby_builder/events/literal.rb +47 -0
  25. data/lib/ripper/ruby_builder/events/method.rb +21 -0
  26. data/lib/ripper/ruby_builder/events/operator.rb +23 -0
  27. data/lib/ripper/ruby_builder/events/statements.rb +50 -0
  28. data/lib/ripper/ruby_builder/events/string.rb +117 -0
  29. data/lib/ripper/ruby_builder/events/symbol.rb +22 -0
  30. data/lib/ripper/ruby_builder/events/while.rb +27 -0
  31. data/lib/ripper/ruby_builder/queue.rb +33 -0
  32. data/lib/ripper/ruby_builder/stack.rb +125 -0
  33. data/lib/ripper/ruby_builder/token.rb +91 -0
  34. data/lib/ripper2ruby.rb +1 -0
  35. data/lib/ruby.rb +28 -0
  36. data/lib/ruby/aggregate.rb +71 -0
  37. data/lib/ruby/alternation/args.rb +25 -0
  38. data/lib/ruby/alternation/hash.rb +25 -0
  39. data/lib/ruby/alternation/list.rb +19 -0
  40. data/lib/ruby/args.rb +36 -0
  41. data/lib/ruby/array.rb +27 -0
  42. data/lib/ruby/assignment.rb +32 -0
  43. data/lib/ruby/assoc.rb +17 -0
  44. data/lib/ruby/block.rb +42 -0
  45. data/lib/ruby/call.rb +34 -0
  46. data/lib/ruby/case.rb +30 -0
  47. data/lib/ruby/const.rb +49 -0
  48. data/lib/ruby/for.rb +18 -0
  49. data/lib/ruby/hash.rb +14 -0
  50. data/lib/ruby/if.rb +35 -0
  51. data/lib/ruby/list.rb +40 -0
  52. data/lib/ruby/literal.rb +45 -0
  53. data/lib/ruby/method.rb +19 -0
  54. data/lib/ruby/node.rb +47 -0
  55. data/lib/ruby/node/composite.rb +68 -0
  56. data/lib/ruby/node/conversions.rb +66 -0
  57. data/lib/ruby/node/position.rb +35 -0
  58. data/lib/ruby/node/source.rb +29 -0
  59. data/lib/ruby/node/text.rb +121 -0
  60. data/lib/ruby/node/traversal.rb +82 -0
  61. data/lib/ruby/operator.rb +49 -0
  62. data/lib/ruby/params.rb +41 -0
  63. data/lib/ruby/statements.rb +45 -0
  64. data/lib/ruby/string.rb +40 -0
  65. data/lib/ruby/symbol.rb +27 -0
  66. data/lib/ruby/token.rb +51 -0
  67. data/lib/ruby/while.rb +32 -0
  68. data/test/all.rb +3 -0
  69. data/test/builder/stack_test.rb +67 -0
  70. data/test/builder/text_test.rb +118 -0
  71. data/test/context_test.rb +54 -0
  72. data/test/erb_stripper_test.rb +29 -0
  73. data/test/fixtures/all.rb.src +150 -0
  74. data/test/fixtures/source_1.rb +16 -0
  75. data/test/fixtures/source_2.rb +1 -0
  76. data/test/fixtures/stuff.rb +371 -0
  77. data/test/fixtures/template.html.erb +22 -0
  78. data/test/fixtures/tmp.rb +6 -0
  79. data/test/lib_test.rb +92 -0
  80. data/test/lib_test_helper.rb +103 -0
  81. data/test/libs.txt +227 -0
  82. data/test/nodes/args_test.rb +100 -0
  83. data/test/nodes/array_test.rb +141 -0
  84. data/test/nodes/assignment_test.rb +49 -0
  85. data/test/nodes/block_test.rb +125 -0
  86. data/test/nodes/call_test.rb +229 -0
  87. data/test/nodes/case_test.rb +68 -0
  88. data/test/nodes/comments_test.rb +25 -0
  89. data/test/nodes/const_test.rb +46 -0
  90. data/test/nodes/conversions_test.rb +9 -0
  91. data/test/nodes/for_test.rb +34 -0
  92. data/test/nodes/hash_test.rb +71 -0
  93. data/test/nodes/heredoc_test.rb +202 -0
  94. data/test/nodes/identifier_test.rb +51 -0
  95. data/test/nodes/if_test.rb +100 -0
  96. data/test/nodes/literals_test.rb +63 -0
  97. data/test/nodes/method_test.rb +92 -0
  98. data/test/nodes/namespaces_test.rb +65 -0
  99. data/test/nodes/node_test.rb +74 -0
  100. data/test/nodes/nodes_test.rb +23 -0
  101. data/test/nodes/operator_test.rb +241 -0
  102. data/test/nodes/separators_test.rb +97 -0
  103. data/test/nodes/statements_test.rb +70 -0
  104. data/test/nodes/string_test.rb +92 -0
  105. data/test/nodes/symbol_test.rb +57 -0
  106. data/test/nodes/unless_test.rb +42 -0
  107. data/test/nodes/until_test.rb +61 -0
  108. data/test/nodes/while_test.rb +71 -0
  109. data/test/test_helper.rb +51 -0
  110. data/test/traversal_test.rb +53 -0
  111. metadata +163 -0
@@ -0,0 +1,27 @@
1
+ class Ripper
2
+ class RubyBuilder < Ripper::SexpBuilder
3
+ module While
4
+ def on_while(expression, statements)
5
+ rdelim = pop_token(:@end)
6
+ ldelim = pop_token(:@do)
7
+ identifier = pop_token(:@while)
8
+ Ruby::While.new(identifier, expression, statements, ldelim, rdelim)
9
+ end
10
+
11
+ def on_while_mod(expression, statement)
12
+ Ruby::WhileMod.new(pop_token(:@while), expression, statement)
13
+ end
14
+
15
+ def on_until(expression, statements)
16
+ rdelim = pop_token(:@end)
17
+ ldelim = pop_token(:@do)
18
+ identifier = pop_token(:@until)
19
+ Ruby::Until.new(identifier, expression, statements, ldelim, rdelim)
20
+ end
21
+
22
+ def on_until_mod(expression, statement)
23
+ Ruby::UntilMod.new(pop_token(:@until), expression, statement)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,33 @@
1
+ # When tokens are pushed to the stack they will be pushed to a queue. Tokens
2
+ # that open new constructs in Ruby (parentheses, semicolons, keywords like
3
+ # class, do, if, etc.) will be held in the queue until the next token is
4
+ # pushed. The queue will then empty itself and return the previously queued
5
+ # token together with the currently pushed token.
6
+ #
7
+ # The reason for this is the way Ripper parses Ruby code. The lexer will fire
8
+ # events every time a known token is found. The parser will fire events when
9
+ # known Ruby constructs are completed. Thus often times when a parser event
10
+ # fires the lexer has already pushed the next (opening) token to the stack.
11
+ #
12
+ # Otoh, event handlers responding to a parser event will want to check for
13
+ # expected tokens (e.g. an opening parentheses for a method call). Thus, when
14
+ # the opening tokens (added by lexer events) are held in a queue while the
15
+ # parser event is fired it will be easier to pop the right tokens belonging
16
+ # to the parser event from the stack.
17
+
18
+ class Ripper
19
+ class RubyBuilder < Ripper::SexpBuilder
20
+ class Queue < ::Array
21
+ def <<(token)
22
+ result = [shift]
23
+ if token.nil?
24
+ elsif token.opener?
25
+ push(token)
26
+ else
27
+ result << token
28
+ end
29
+ result.compact
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,125 @@
1
+ require 'ripper/ruby_builder/queue'
2
+ require 'ripper/ruby_builder/buffer'
3
+
4
+ # The stack holds the current "state" of the parser and facilitates communication
5
+ # between lexer events (which fire on known Ruby tokens) and parser events (which
6
+ # fire on known Ruby constructs).
7
+ #
8
+ # E.g. when the Ruby code foo(:bar) is parsed the lexer will fire events for
9
+ # the identifiers 'foo' and 'bar', for the opening and closing parentheses and
10
+ # for the colon . Tt fires the events in the order of the occurence of the
11
+ # tokens from left to right. RubyBuilder pushes all these tokens to the stack.
12
+ #
13
+ # See RubyBuilder::Queue and RubyBuilder::Buffer for what else happens when a
14
+ # token is pushed to the stack.
15
+ #
16
+ # The parser on the other hand will fire events for known Ruby constructs such
17
+ # as the argument list, arguments being added to the argument list, the method
18
+ # call etc. The parser fires these events in the order of Ruby constructs being
19
+ # recognized - i.e. when they are completed. RubyBuilder responds to these
20
+ # events and will pop tokens off from the stack as required (e.g. for
21
+ # constructing an argument list it will try to pop off the corresponding
22
+ # left and right parentheses.)
23
+ #
24
+ # When RubyBuilder pops tokens off from the stack it wants to be careful not to
25
+ # pop off tokens that belong to higher level constructs that haven't yet fired.
26
+ # E.g. for a nested method call foo(bar(1)) the inner call fires first because
27
+ # it completes first. Thus, when RubyBuilder constructs this call it must not
28
+ # pop off the opening parentheses belonging to the outer call (which of course)
29
+ # is already on the stack.
30
+ #
31
+ # For that reason when popping off tokens the stack by default stops searching
32
+ # for the token when an opening token is found. RubyBuilder can force it to
33
+ # search past opening tokens by setting the :pass option to true. Similarly
34
+ # RubyBuilder can set constraints to what tokens it wants to be popped off:
35
+ #
36
+ # :pass => true # search past opening tokens
37
+ # :max => count # number of tokens
38
+ # :value => 'foo' # value of the token
39
+ # :pos => pos # position of the token
40
+ # :left => token # token must be located right of the given token
41
+ # :right => token # token must be located left of the given token
42
+ # :reverse => true # searches the stack in reverse order (i.e. tokens are shifted)
43
+
44
+ class Ripper
45
+ class RubyBuilder < Ripper::SexpBuilder
46
+ class Stack < ::Array
47
+ attr_reader :queue, :buffer
48
+
49
+ def initialize
50
+ @queue = Queue.new
51
+ @buffer = Buffer.new
52
+ end
53
+
54
+ def push(token)
55
+ return token if buffer.aggregate(token)
56
+ tokens = queue << token
57
+ tokens.each do |token|
58
+ self << token
59
+ end
60
+ end
61
+
62
+ alias :_pop :pop
63
+ def pop(*types)
64
+ options = types.last.is_a?(::Hash) ? types.pop : {}
65
+ max, pass, revert = options.delete_at(:max, :pass, :reverse)
66
+ ignored, tokens = [], []
67
+ reverse! if revert
68
+
69
+ while !empty? && !(max && tokens.length >= max)
70
+ if types.include?(last.type) && matches?(options)
71
+ tokens << _pop()
72
+ elsif last.opener? && !pass
73
+ break
74
+ else
75
+ ignored.unshift(_pop())
76
+ end
77
+ end
78
+
79
+ replace(self + ignored)
80
+ reverse! if revert
81
+ tokens
82
+ end
83
+
84
+ protected
85
+
86
+ def matches?(conditions)
87
+ conditions.inject(true) do |result, (type, value)|
88
+ result && case type
89
+ when :value
90
+ has_value?(value)
91
+ when :pos
92
+ at?(value)
93
+ when :right
94
+ left_of?(value)
95
+ when :left
96
+ right_of?(value)
97
+ end
98
+ end
99
+ end
100
+
101
+ def at?(pos)
102
+ pos.nil? || last.position == pos
103
+ end
104
+
105
+ def left_of?(right)
106
+ right.nil? || last.nil? || last < right
107
+ end
108
+
109
+ def right_of?(left)
110
+ left.nil? || last.nil? || left < last
111
+ end
112
+
113
+ def has_value?( value)
114
+ case value
115
+ when nil
116
+ true
117
+ when ::Array
118
+ value.include?(last.value)
119
+ else
120
+ last.value == value
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,91 @@
1
+ require 'ruby/node/position'
2
+
3
+ # Tokens are simple value objects that hold the token type, value and position.
4
+ # There are a bunch of helper methods to check the token type and convert the
5
+ # token to Ruby nodes.
6
+ #
7
+ # We mostly operate with Ripper's token types (such as :@ident etc.). For Ripper's
8
+ # sexp types :@kw (keyword) and :@op we use more specific token types based on
9
+ # the sexp's value. E.g. Ripper's sexp [:@op, '+', [0, 0]] would become a token
10
+ # with the type :@+.
11
+
12
+ class Ripper
13
+ class RubyBuilder < Ripper::SexpBuilder
14
+ class Token
15
+ include Comparable
16
+
17
+ attr_accessor :type, :token, :position, :prolog
18
+
19
+ def initialize(type = nil, token = nil, position = nil)
20
+ @type = token_type(type, token)
21
+ @token = token
22
+ @position = position if position
23
+ end
24
+
25
+ def newline?
26
+ NEWLINE.include?(type)
27
+ end
28
+
29
+ def whitespace?
30
+ WHITESPACE.include?(type)
31
+ end
32
+
33
+ def opener?
34
+ OPENERS.include?(type)
35
+ end
36
+
37
+ def keyword?
38
+ KEYWORDS.include?(type)
39
+ end
40
+
41
+ def operator?
42
+ OPERATORS.include?(type)
43
+ end
44
+
45
+ def separator?
46
+ SEPARATORS.include?(type)
47
+ end
48
+
49
+ def prolog?
50
+ whitespace? or separator? or heredoc?
51
+ end
52
+
53
+ def known?
54
+ keyword? || operator? || opener? || whitespace? || [:@backtick].include?(type)
55
+ end
56
+
57
+ def comment?
58
+ type == :@comment
59
+ end
60
+
61
+ def heredoc?
62
+ type == :@heredoc
63
+ end
64
+
65
+ def to_sexp
66
+ [type, token, [row + 1, column]]
67
+ end
68
+
69
+ def to_identifier
70
+ Ruby::Identifier.new(token, position, prolog)
71
+ end
72
+
73
+ def <=>(other)
74
+ position <=> (other.respond_to?(:position) ? other.position : other)
75
+ end
76
+
77
+ protected
78
+
79
+ def token_type(type, token)
80
+ case type
81
+ when :@kw
82
+ :"@#{token.gsub(/\W/, '')}"
83
+ when :@op
84
+ :"@#{token}"
85
+ else
86
+ type
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1 @@
1
+ require 'ripper/ruby_builder'
data/lib/ruby.rb ADDED
@@ -0,0 +1,28 @@
1
+ Dir[File.dirname(__FILE__) + '/ruby/*.rb'].each do |file|
2
+ require "ruby/#{File.basename(file)}"
3
+ end
4
+
5
+ # Object oriented representation of Ruby code.
6
+ #
7
+ # The base class is Ruby::Node. It facilitates
8
+ #
9
+ # * a composite pattern (see Ruby::Node::Composite)
10
+ # * means for extracting from the original source (see Ruby::Node::Source)
11
+ #
12
+ # There are two main concrete classes derived from Node: Token and Aggregate.
13
+ #
14
+ # Tokens are "atomic" node types that represent non-composite Ruby constructs
15
+ # such as Keyword, Identifier, StringContent and literal types such as integers,
16
+ # floats, true, false, nil etc. Aggregates are composed node types that hold
17
+ # one or many tokens, such as Class, Module, Block, If, For, Case, While etc.
18
+ #
19
+ # Each node type supports the to_ruby method which will return an exact copy
20
+ # of the orginal code it was parsed from.
21
+ #
22
+ # There are also a few helper methods for converting a node to another type
23
+ # (see Ruby::Node::Conversions) and very few helper methods for altering
24
+ # existing code structures (see Ruby::Alternation).
25
+
26
+ module Ruby
27
+ include Conversions
28
+ end
@@ -0,0 +1,71 @@
1
+ require 'ruby/node'
2
+
3
+ module Ruby
4
+ class Aggregate < Node
5
+ def position(prolog = false)
6
+ nodes = self.nodes
7
+ nodes.unshift(self.prolog) if prolog
8
+ nodes.compact.each { |n| return n.position.dup if n } && nil
9
+ end
10
+
11
+ def position=(position)
12
+ nodes.each { |n| return n.position = position if n }
13
+ end
14
+
15
+ def prolog
16
+ nodes.each { |n| return n.prolog if n } && nil
17
+ end
18
+
19
+ def prolog=(prolog)
20
+ nodes.each { |n| return n.prolog = prolog if n }
21
+ end
22
+
23
+ def to_ruby(prolog = false)
24
+ nodes = self.nodes.compact
25
+ (nodes.shift.try(:to_ruby, prolog) || '') + nodes.map { |node| node.to_ruby(true) }.join
26
+ end
27
+ end
28
+
29
+ class DelimitedAggregate < Aggregate
30
+ child_accessor :ldelim, :rdelim
31
+
32
+ def initialize(ldelim = nil, rdelim = nil)
33
+ self.ldelim = ldelim
34
+ self.rdelim = rdelim
35
+ end
36
+ end
37
+
38
+ class NamedAggregate < DelimitedAggregate
39
+ child_accessor :identifier
40
+
41
+ def initialize(identifier, ldelim = nil, rdelim = nil)
42
+ self.identifier = identifier
43
+ super(ldelim, rdelim)
44
+ end
45
+ end
46
+
47
+ require 'ruby/token'
48
+ class Variable < Token # TODO join with DelimitedVariable
49
+ end
50
+
51
+ class DelimitedVariable < DelimitedAggregate
52
+ child_accessor :identifier
53
+
54
+ def initialize(identifier, ldelim = nil)
55
+ self.identifier = identifier
56
+ super(ldelim)
57
+ end
58
+
59
+ def value
60
+ identifier.token.to_sym
61
+ end
62
+
63
+ def nodes
64
+ [ldelim, identifier].compact
65
+ end
66
+
67
+ def method_missing(method, *args, &block)
68
+ identifier.respond_to?(method) ? identifier.send(method, *args, &block) : super
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,25 @@
1
+ module Ruby
2
+ module Alternation
3
+ module ArgsList
4
+ def options
5
+ last.arg.is_a?(Ruby::Hash) ? last : nil
6
+ end
7
+
8
+ def set_option(key, value)
9
+ if options.nil?
10
+ # TODO gotta add a separator as well, so maybe better replace the whole options hash
11
+ self << to_node({key => value}, position.tap { |p| p[1] += length })
12
+ else
13
+ options[key] = to_node(value, options[key].position, options[key].prolog)
14
+ end
15
+ end
16
+
17
+ protected
18
+
19
+ def to_node(arg, position = nil, prolog = nil)
20
+ arg = super
21
+ arg.is_a?(Arg) ? arg : Arg.new(arg)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ module Ruby
2
+ module Alternation
3
+ module Hash
4
+ def [](key)
5
+ each { |assoc| return assoc.value if assoc.key.value == key } or nil
6
+ end
7
+
8
+ def []=(key, value)
9
+ value = Ruby.from_native(value, nil, ' ') unless value.is_a?(Node)
10
+ if assoc = detect { |assoc| assoc.key.value == key }
11
+ assoc.value = value
12
+ else
13
+ # # TODO never happens, fix positions
14
+ # separators << Token.new(',')
15
+ # elements << Assoc.new(key, value)
16
+ # self[key]
17
+ end
18
+ end
19
+
20
+ def delete(key)
21
+ delete_if { |assoc| assoc.key.value == key }
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ module Ruby
2
+ module Alternation
3
+ module List
4
+ def <<(element)
5
+ elements << element
6
+ self
7
+ end
8
+
9
+ def pop
10
+ [elements.pop]
11
+ end
12
+
13
+ def []=(ix, element)
14
+ element = to_node(element, self[ix].position, self[ix].prolog)
15
+ super
16
+ end
17
+ end
18
+ end
19
+ end