ripper2ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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