/
ruby_builder.rb
163 lines (129 loc) · 5.28 KB
/
ruby_builder.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
require 'ripper'
require 'ruby'
require 'core_ext/hash/delete_at'
require 'core_ext/array/flush'
require 'ripper/ruby_builder/token'
require 'ripper/ruby_builder/stack'
Dir[File.dirname(__FILE__) + '/ruby_builder/events/*.rb'].each { |file| require file }
# Ripper::RubyBuilder extends Ripper's SexpBuilder and builds a rich, object
# oriented representation of Ruby code.
#
# code = Ripper::RubyBuilder.build("foo(1, :bar, %w(baz)"), filename)
# code.to_ruby # => "foo(1, :bar, %w(baz)"
#
# RubyBuilder uses SexpBuilder's lexing and parsing event callbacks (see
# ruby_builder/events) and builds up Ruby::Nodes which can then be used.
# See RubyBuilder::Stack, RubyBuilder::Queue, RubyBuilder::Buffer and
# RubyBuilder::Token for more details about the parsing process.
class Ripper
class RubyBuilder < Ripper::SexpBuilder
class ParseError < RuntimeError
end
class << self
def build(src, filename = nil)
new(src, filename).parse
end
end
NEWLINE = [:@nl, :@ignored_nl]
WHITESPACE = [:@sp, :@comment] + NEWLINE
OPENERS = [:@lparen, :@lbracket, :@lbrace, :@class, :@module, :@def, :@begin, :@while, :@until,
:@for, :@if, :@elsif, :@else, :@unless, :@case, :@when, :@embexpr_beg, :@do, :@rescue,
:'@=', :'@::']
KEYWORDS = [:@alias, :@and, :@BEGIN, :@begin, :@break, :@case, :@class, :@def, :@defined,
:@do, :@else, :@elsif, :@END, :@end, :@ensure, :@false, :@for, :@if, :@in,
:@module, :@next, :@nil, :@not, :@or, :@redo, :@rescue, :@retry, :@return,
:@self, :@super, :@then, :@true, :@undef, :@unless, :@until, :@when, :@while,
:@yield]
SEPARATORS = [:@semicolon, :@comma]
UNARY_OPERATORS = [:'@+', :'@-', :'@!', :'@~', :@not, :'@+@', :'@-@']
BINARY_OPERATORS = [:'@**', :'@*', :'@/', :'@%', :'@+', :'@-', :'@<<', :'@>>', :'@&', :'@|', :'@^',
:'@>', :'@>=', :'@<', :'@<=', :'@<=>', :'@==', :'@===', :'@!=', :'@=~', :'@!~',
:'@&&', :'@||', :@and, :@or]
TERNARY_OPERATORS = [:'@?', :'@:']
ASSIGN_OPERATORS = [:'@=', :'@+=', :'@-=', :'@*=', :'@**=', :'@%=', :'@/=', :'@|=', :'@&=', :'@^=',
:'@[]=', :'@||=', :'@&&=', :'@<<=', :'@>>=']
ACCESS_OPERATORS = [:'@[]']
OPERATORS = UNARY_OPERATORS + BINARY_OPERATORS + TERNARY_OPERATORS + ASSIGN_OPERATORS + ACCESS_OPERATORS
include Lexer, Statements, Const, Method, Call, Block, Args, Assignment, Operator,
If, Case, For, While, Identifier, Literal, String, Symbol, Array, Hash
attr_reader :src, :filename, :stack, :string_stack, :trailing_whitespace
def initialize(src, filename = nil, lineno = nil)
@src = src ||= filename && File.read(filename) || ''
@src.gsub!(/([\s\n]*)\Z/) { |s| @trailing_whitespace = Ruby::Whitespace.new(s) and nil }
@filename = filename
@stack = []
@stack = Stack.new
@string_stack = []
super
end
protected
def position
Ruby::Node::Position.new(lineno.to_i - 1, column)
end
def prolog
Ruby::Prolog.new(stack.buffer.flush)
end
def push(sexp = nil)
token = Token.new(sexp[0], sexp[1], position) if sexp.is_a?(::Array)
stack.push(token)
token
end
def pop(*args)
stack.pop(*args)
end
def pop_token(*types)
options = types.last.is_a?(::Hash) ? types.pop : {}
options[:max] = 1
pop_tokens(*types << options).first
end
def pop_tokens(*types)
pop(*types).map { |token| build_token(token) }.flatten.compact
end
def pop_identifier(type, options = {})
pop_token(type, options).to_identifier
end
def pop_string_content
pop_token(:@tstring_content).to_string_content
end
def pop_operator(options = {})
pop_token(*OPERATORS, options)
end
def pop_unary_operator(options = {})
pop_token(*UNARY_OPERATORS, options)
end
def pop_binary_operator(options = {})
pop_token(*BINARY_OPERATORS, options)
end
def pop_ternary_operator(options = {})
pop_token(*TERNARY_OPERATORS, options)
end
def pop_assignment_operator(options = {})
pop_token(*ASSIGN_OPERATORS, options)
end
def build_token(token)
Ruby::Token.new(token.token, token.position, token.prolog) if token
end
def build_keyword(token)
klass = Ruby.const_get(token.token[0].upcase + token.token[1..-1])
klass.new(token, token.position, token.prolog)
rescue NameError
Ruby::Keyword.new(token, token.position, token.prolog)
end
def build_xstring(token)
case token.type
when :@symbeg
Ruby::DynaSymbol.new(nil, build_token(token))
when :@regexp_beg
Ruby::Regexp.new(nil, build_token(token))
else
Ruby::ExecutableString.new(nil, build_token(token))
end
end
# def extract_src(from, to)
# lines = Ruby::Node::Text.split(src)
# lines[from.row] = lines[from.row][from.col..-1] # || ''
# lines[to.row] = lines[to.row][0, to.col]
# lines[from.row..to.row].join
# end
end
end