to_source 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rvmrc +1 -0
- data/.travis.yml +3 -0
- data/Gemfile +5 -0
- data/Rakefile +10 -0
- data/Readme.md +31 -0
- data/lib/to_source/core_ext/node.rb +19 -0
- data/lib/to_source/version.rb +3 -0
- data/lib/to_source/visitor.rb +237 -0
- data/lib/to_source.rb +16 -0
- data/test/to_source/visitor_test.rb +126 -0
- data/to_source.gemspec +18 -0
- metadata +58 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use rbx-head@to_source --create
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# to_source [![Build Status](https://secure.travis-ci.org/txus/to_source.png)](http://travis-ci.org/txus/to_source)
|
2
|
+
|
3
|
+
to_source is a little Rubinius gem that enables Abstract Syntax Tree nodes to
|
4
|
+
transform themselves into source code. It's the reverse of Rubinius' builtin
|
5
|
+
`#to_ast` method. See for yourself:
|
6
|
+
|
7
|
+
#!/bin/rbx
|
8
|
+
some_code = "a = 123"
|
9
|
+
ast = some_code.to_ast
|
10
|
+
# => #<Rubinius::AST::LocalVariableAssignment:0x21b8
|
11
|
+
@value=#<Rubinius::AST::FixnumLiteral:0x21bc @value=123 @line=1>
|
12
|
+
@variable=nil @line=1 @name=:a>
|
13
|
+
|
14
|
+
ast.to_source
|
15
|
+
# => "a = 123"
|
16
|
+
|
17
|
+
## Installing
|
18
|
+
|
19
|
+
to_source needs Rubinius 2.0 to run, in either 1.8 or 1.9 mode.
|
20
|
+
|
21
|
+
Put this in your Gemfile:
|
22
|
+
|
23
|
+
gem 'to_source'
|
24
|
+
|
25
|
+
And just call `#to_source` in any AST node!
|
26
|
+
|
27
|
+
## Who's this
|
28
|
+
|
29
|
+
This was made by [Josep M. Bach (Txus)](http://txustice.me) under the MIT
|
30
|
+
license. I'm [@txustice](http://twitter.com/txustice) on twitter (where you
|
31
|
+
should probably follow me!).
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Rubinius
|
2
|
+
module AST
|
3
|
+
class Node
|
4
|
+
# Public: Works like #visit, but it doesn't visit the children just yet;
|
5
|
+
# instead, lets the visitor decide when and how to do it.
|
6
|
+
#
|
7
|
+
# visitor - The visitor object. It must respond to methods named after the
|
8
|
+
# node names.
|
9
|
+
#
|
10
|
+
# Returns nothing.
|
11
|
+
def lazy_visit(visitor, parent=nil, indent=false)
|
12
|
+
args = [self.node_name, self, parent]
|
13
|
+
args.push true if indent
|
14
|
+
|
15
|
+
visitor.__send__ *args
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,237 @@
|
|
1
|
+
module ToSource
|
2
|
+
class Visitor
|
3
|
+
def initialize
|
4
|
+
@output = []
|
5
|
+
@indentation = 0
|
6
|
+
end
|
7
|
+
|
8
|
+
def emit(code)
|
9
|
+
@output.push code
|
10
|
+
end
|
11
|
+
|
12
|
+
def output
|
13
|
+
@output.join
|
14
|
+
end
|
15
|
+
|
16
|
+
def current_indentation
|
17
|
+
' ' * @indentation
|
18
|
+
end
|
19
|
+
|
20
|
+
def local_variable_assignment(node, parent)
|
21
|
+
emit "%s = " % node.name
|
22
|
+
node.value.lazy_visit self, node
|
23
|
+
end
|
24
|
+
|
25
|
+
def local_variable_access(node, parent)
|
26
|
+
emit node.name
|
27
|
+
end
|
28
|
+
|
29
|
+
def fixnum_literal(node, parent)
|
30
|
+
emit node.value.to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
def float_literal(node, parent)
|
34
|
+
emit node.value.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def string_literal(node, parent)
|
38
|
+
emit '"' << node.string.to_s << '"'
|
39
|
+
end
|
40
|
+
|
41
|
+
def symbol_literal(node, parent)
|
42
|
+
emit ':' << node.value.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
def true_literal(node, parent)
|
46
|
+
emit 'true'
|
47
|
+
end
|
48
|
+
|
49
|
+
def false_literal(node, parent)
|
50
|
+
emit 'false'
|
51
|
+
end
|
52
|
+
|
53
|
+
def nil_literal(node, parent)
|
54
|
+
emit 'nil'
|
55
|
+
end
|
56
|
+
|
57
|
+
def array_literal(node, parent)
|
58
|
+
body = node.body
|
59
|
+
|
60
|
+
emit '['
|
61
|
+
body.each_with_index do |node, index|
|
62
|
+
node.lazy_visit self, node
|
63
|
+
emit ', ' unless body.length == index + 1 # last element
|
64
|
+
end
|
65
|
+
emit ']'
|
66
|
+
end
|
67
|
+
|
68
|
+
def hash_literal(node, parent)
|
69
|
+
body = node.array.each_slice(2)
|
70
|
+
|
71
|
+
emit '{'
|
72
|
+
body.each_with_index do |slice, index|
|
73
|
+
key, value = slice
|
74
|
+
|
75
|
+
key.lazy_visit self, node
|
76
|
+
emit " => "
|
77
|
+
value.lazy_visit self, node
|
78
|
+
|
79
|
+
emit ', ' unless body.to_a.length == index + 1 # last element
|
80
|
+
end
|
81
|
+
emit '}'
|
82
|
+
end
|
83
|
+
|
84
|
+
def range(node, parent)
|
85
|
+
node.start.lazy_visit self, node
|
86
|
+
emit '..'
|
87
|
+
node.finish.lazy_visit self, node
|
88
|
+
end
|
89
|
+
|
90
|
+
def regex_literal(node, parent)
|
91
|
+
emit '/'
|
92
|
+
emit node.source
|
93
|
+
emit '/'
|
94
|
+
end
|
95
|
+
|
96
|
+
def send(node, parent)
|
97
|
+
unless node.receiver.is_a?(Rubinius::AST::Self)
|
98
|
+
node.receiver.lazy_visit self, node
|
99
|
+
emit '.'
|
100
|
+
end
|
101
|
+
emit node.name
|
102
|
+
|
103
|
+
if node.block
|
104
|
+
emit ' '
|
105
|
+
node.block.lazy_visit self, node if node.block
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def send_with_arguments(node, parent)
|
110
|
+
return if process_binary_operator(node, parent) # 1 * 2, a / 3, true && false
|
111
|
+
|
112
|
+
unless node.receiver.is_a?(Rubinius::AST::Self)
|
113
|
+
node.receiver.lazy_visit self, node
|
114
|
+
emit '.'
|
115
|
+
end
|
116
|
+
|
117
|
+
emit node.name
|
118
|
+
emit '('
|
119
|
+
node.arguments.lazy_visit self, node
|
120
|
+
emit ')'
|
121
|
+
if node.block
|
122
|
+
emit ' '
|
123
|
+
node.block.lazy_visit self, node if node.block
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def actual_arguments(node, parent)
|
128
|
+
body = node.array
|
129
|
+
body.each_with_index do |argument, index|
|
130
|
+
argument.lazy_visit self, parent
|
131
|
+
emit ', ' unless body.length == index + 1 # last element
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def iter_arguments(node, parent)
|
136
|
+
body = if node.prelude == :single
|
137
|
+
Array(node.arguments.name)
|
138
|
+
else
|
139
|
+
node.arguments.left.body.map(&:name)
|
140
|
+
end
|
141
|
+
|
142
|
+
emit '|'
|
143
|
+
body.each_with_index do |argument, index|
|
144
|
+
emit argument.to_s
|
145
|
+
emit ', ' unless body.length == index + 1 # last element
|
146
|
+
end
|
147
|
+
emit '|'
|
148
|
+
end
|
149
|
+
|
150
|
+
def iter(node, parent)
|
151
|
+
emit 'do'
|
152
|
+
|
153
|
+
if node.arguments && node.arguments.arity != -1
|
154
|
+
emit ' '
|
155
|
+
node.arguments.lazy_visit self, parent
|
156
|
+
end
|
157
|
+
|
158
|
+
emit "\n"
|
159
|
+
@indentation += 1
|
160
|
+
|
161
|
+
if node.body.is_a?(Rubinius::AST::Block)
|
162
|
+
node.body.lazy_visit self, parent, true
|
163
|
+
else
|
164
|
+
emit current_indentation
|
165
|
+
node.body.lazy_visit self, parent
|
166
|
+
end
|
167
|
+
|
168
|
+
emit "\n"
|
169
|
+
emit 'end'
|
170
|
+
end
|
171
|
+
|
172
|
+
def block(node, parent, indent=false)
|
173
|
+
body = node.array
|
174
|
+
body.each_with_index do |expression, index|
|
175
|
+
emit current_indentation if indent
|
176
|
+
expression.lazy_visit self, parent
|
177
|
+
emit "\n" unless body.length == index + 1 # last element
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def not(node, parent)
|
182
|
+
emit '!'
|
183
|
+
node.value.lazy_visit self, parent
|
184
|
+
end
|
185
|
+
|
186
|
+
def and(node, parent)
|
187
|
+
node.left.lazy_visit self, node
|
188
|
+
emit ' && '
|
189
|
+
node.right.lazy_visit self, node
|
190
|
+
end
|
191
|
+
|
192
|
+
def or(node, parent)
|
193
|
+
node.left.lazy_visit self, node
|
194
|
+
emit ' || '
|
195
|
+
node.right.lazy_visit self, node
|
196
|
+
end
|
197
|
+
|
198
|
+
def op_assign_and(node, parent)
|
199
|
+
node.left.lazy_visit self, node
|
200
|
+
emit ' && '
|
201
|
+
node.right.lazy_visit self, node
|
202
|
+
end
|
203
|
+
|
204
|
+
def op_assign_or(node, parent)
|
205
|
+
node.left.lazy_visit self, node
|
206
|
+
emit ' || '
|
207
|
+
node.right.lazy_visit self, node
|
208
|
+
end
|
209
|
+
|
210
|
+
def constant_access(node, parent)
|
211
|
+
emit node.name
|
212
|
+
end
|
213
|
+
|
214
|
+
def scoped_constant(node, parent)
|
215
|
+
node.parent.lazy_visit self, node
|
216
|
+
emit '::'
|
217
|
+
emit node.name
|
218
|
+
end
|
219
|
+
|
220
|
+
private
|
221
|
+
|
222
|
+
def process_binary_operator(node, parent)
|
223
|
+
operators = %w(+ - * / & | <<).map(&:to_sym)
|
224
|
+
return false unless operators.include?(node.name)
|
225
|
+
return false if node.arguments.array.length != 1
|
226
|
+
|
227
|
+
operand = node.arguments.array[0]
|
228
|
+
|
229
|
+
unless node.receiver.is_a?(Rubinius::AST::Self)
|
230
|
+
node.receiver.lazy_visit self, node
|
231
|
+
end
|
232
|
+
|
233
|
+
emit ' ' << node.name.to_s << ' '
|
234
|
+
operand.lazy_visit self, node
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
data/lib/to_source.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "to_source/version"
|
2
|
+
require "to_source/core_ext/node"
|
3
|
+
require "to_source/visitor"
|
4
|
+
|
5
|
+
module ToSource
|
6
|
+
# Public: Converts the node back to its original source code.
|
7
|
+
#
|
8
|
+
# Returns the String output.
|
9
|
+
def to_source
|
10
|
+
visitor = Visitor.new
|
11
|
+
lazy_visit(visitor)
|
12
|
+
visitor.output
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Rubinius::AST::Node.send :include, ToSource
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'to_source'
|
3
|
+
|
4
|
+
module ToSource
|
5
|
+
class VisitorTest < Test::Unit::TestCase
|
6
|
+
def visit(code)
|
7
|
+
code.to_ast.to_source
|
8
|
+
end
|
9
|
+
|
10
|
+
def assert_source(code)
|
11
|
+
assert_equal code, visit(code)
|
12
|
+
end
|
13
|
+
|
14
|
+
def assert_converts(expected, code)
|
15
|
+
assert_equal expected, visit(code)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_local_assignment
|
19
|
+
assert_source "foo = 1"
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_local_access
|
23
|
+
assert_source "foo = 1\nfoo"
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_constant_access
|
27
|
+
assert_source "Rubinius"
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_scoped_constant_access
|
31
|
+
assert_source "Rubinius::Debugger"
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_fixnum_literal
|
35
|
+
assert_source "1"
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_float_literal
|
39
|
+
assert_source "1.0"
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_string_literal
|
43
|
+
assert_source '"foo"'
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_symbol_literal
|
47
|
+
assert_source ':foo'
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_true_literal
|
51
|
+
assert_source 'true'
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_false_literal
|
55
|
+
assert_source 'false'
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_nil_literal
|
59
|
+
assert_source 'nil'
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_array_literal
|
63
|
+
assert_source '[1, 2, 3]'
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_hash_literal
|
67
|
+
assert_source '{:answer => 42, :bar => :baz}'
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_range
|
71
|
+
assert_source '20..34'
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_regex
|
75
|
+
assert_source '/.*/'
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_send
|
79
|
+
assert_source 'foo.bar'
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_send_with_arguments
|
83
|
+
assert_converts 'foo.bar(:baz, :yeah)', 'foo.bar :baz, :yeah'
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_send_with_arguments_and_empty_block
|
87
|
+
assert_converts "foo.bar(:baz, :yeah) do\n nil\nend", "foo.bar(:baz, :yeah) do\nend"
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_send_with_arguments_and_block_with_one_argument
|
91
|
+
assert_source "foo.bar(:baz, :yeah) do |a|\n 3\n 4\nend"
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_send_with_arguments_and_block_with_arguments
|
95
|
+
assert_source "foo.bar(:baz, :yeah) do |a, b|\n 3\n 4\nend"
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_lambda
|
99
|
+
assert_source "lambda do |a, b|\n a\nend"
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_proc
|
103
|
+
assert_source "Proc.new do\n a\nend"
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_binary_operators
|
107
|
+
%w(+ - * / & | && || <<).each do |operator|
|
108
|
+
assert_source "1 #{operator} 2"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_expands_binary_equal
|
113
|
+
assert_converts "a = a + 2", "a += 2"
|
114
|
+
assert_converts "a = a - 2", "a -= 2"
|
115
|
+
assert_converts "a = a * 2", "a *= 2"
|
116
|
+
assert_converts "a = a / 2", "a /= 2"
|
117
|
+
assert_converts "a && a = 2", "a &&= 2"
|
118
|
+
assert_converts "a || a = 2", "a ||= 2"
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_unary_operators
|
122
|
+
assert_source "!1"
|
123
|
+
assert_source "!!1"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/to_source.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "to_source/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "to_source"
|
7
|
+
s.version = ToSource::VERSION
|
8
|
+
s.authors = ["Josep M. Bach"]
|
9
|
+
s.email = ["josep.m.bach@gmail.com"]
|
10
|
+
s.homepage = "http://github.com/txus/to_source"
|
11
|
+
s.summary = %q{Transform your Rubinius AST nodes back to source code. Reverse parsing!}
|
12
|
+
s.description = %q{Transform your Rubinius AST nodes back to source code. Reverse parsing!}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: to_source
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josep M. Bach
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-23 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Transform your Rubinius AST nodes back to source code. Reverse parsing!
|
15
|
+
email:
|
16
|
+
- josep.m.bach@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .gitignore
|
22
|
+
- .rvmrc
|
23
|
+
- .travis.yml
|
24
|
+
- Gemfile
|
25
|
+
- Rakefile
|
26
|
+
- Readme.md
|
27
|
+
- lib/to_source.rb
|
28
|
+
- lib/to_source/core_ext/node.rb
|
29
|
+
- lib/to_source/version.rb
|
30
|
+
- lib/to_source/visitor.rb
|
31
|
+
- test/to_source/visitor_test.rb
|
32
|
+
- to_source.gemspec
|
33
|
+
homepage: http://github.com/txus/to_source
|
34
|
+
licenses: []
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
requirements: []
|
52
|
+
rubyforge_project:
|
53
|
+
rubygems_version: 1.8.12
|
54
|
+
signing_key:
|
55
|
+
specification_version: 3
|
56
|
+
summary: Transform your Rubinius AST nodes back to source code. Reverse parsing!
|
57
|
+
test_files:
|
58
|
+
- test/to_source/visitor_test.rb
|