to_source 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.
- 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 [](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
|