unparser 0.5.3 → 0.6.0
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.
- checksums.yaml +4 -4
- data/README.md +12 -0
- data/lib/unparser.rb +34 -24
- data/lib/unparser/abstract_type.rb +121 -0
- data/lib/unparser/adamantium.rb +150 -0
- data/lib/unparser/adamantium/method_builder.rb +110 -0
- data/lib/unparser/anima.rb +184 -0
- data/lib/unparser/anima/attribute.rb +59 -0
- data/lib/unparser/anima/error.rb +23 -0
- data/lib/unparser/ast.rb +1 -2
- data/lib/unparser/color.rb +1 -1
- data/lib/unparser/concord.rb +114 -0
- data/lib/unparser/diff.rb +1 -1
- data/lib/unparser/either.rb +153 -0
- data/lib/unparser/emitter.rb +1 -1
- data/lib/unparser/emitter/flow_modifier.rb +11 -2
- data/lib/unparser/emitter/hash.rb +1 -31
- data/lib/unparser/emitter/if.rb +8 -0
- data/lib/unparser/emitter/in_pattern.rb +2 -0
- data/lib/unparser/emitter/index.rb +1 -1
- data/lib/unparser/emitter/kwargs.rb +13 -0
- data/lib/unparser/emitter/match_pattern.rb +22 -0
- data/lib/unparser/emitter/op_assign.rb +2 -2
- data/lib/unparser/emitter/pair.rb +33 -0
- data/lib/unparser/emitter/primitive.rb +2 -2
- data/lib/unparser/emitter/simple.rb +2 -2
- data/lib/unparser/emitter/splat.rb +11 -1
- data/lib/unparser/equalizer.rb +98 -0
- data/lib/unparser/generation.rb +3 -1
- data/lib/unparser/node_details.rb +1 -1
- data/lib/unparser/node_helpers.rb +1 -0
- data/lib/unparser/validation.rb +3 -3
- data/lib/unparser/writer/binary.rb +1 -1
- data/lib/unparser/writer/dynamic_string.rb +13 -23
- data/lib/unparser/writer/rescue.rb +5 -1
- data/lib/unparser/writer/send.rb +7 -16
- metadata +32 -114
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a585e06cf743ca1901a601efa2fb7f178528307f89a6bea190df19014e3abf3
|
4
|
+
data.tar.gz: dc1980af7f00fa4b54da6fc91cc740857588bf5d1bf30f6c362be995a86a2d83
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: da5524a1621bd1202f943c201b79b425ae4c203a9642a633b2a8a66fc85a0b8d3a250464c18432b791ce7e656c5b1a3940bc93d004f0bc18aa5413f931fc74bf
|
7
|
+
data.tar.gz: ab9cd266e002fcf85a12e1051a6b6a9fb9f47479ac2d7d883abcb99306d09ea6a196cb5d7de98a8c4f8a6635a78284e14e689cc2786f0a994eb504db0a0545ee
|
data/README.md
CHANGED
@@ -168,6 +168,18 @@ People
|
|
168
168
|
|
169
169
|
Various people contributed to this repository. See [Contributors](https://github.com/mbj/unparser/graphs/contributors).
|
170
170
|
|
171
|
+
Included Libraries
|
172
|
+
------------------
|
173
|
+
|
174
|
+
For dependency reduction reasons unparser ships vendored (and reduced) versions of:
|
175
|
+
|
176
|
+
* [abstract_type](https://github.com/mbj/concord) -> Unparser::AbstractType
|
177
|
+
* [adamantium](https://github.com/dkubb/adamantium) -> Unparser::Adamantium
|
178
|
+
* [anima](https://github.com/mbj/concord) -> Unparser::Anima
|
179
|
+
* [concord](https://github.com/mbj/concord) -> Unparser::Concord
|
180
|
+
* [memoizable](https://github.com/dkubb/memoizable) -> Unparser::Adamantium
|
181
|
+
* [mprelude](https://github.com/dkubb/memoizable) -> Unparser::Either
|
182
|
+
|
171
183
|
Contributing
|
172
184
|
-------------
|
173
185
|
|
data/lib/unparser.rb
CHANGED
@@ -1,16 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'abstract_type'
|
4
|
-
require 'anima'
|
5
|
-
require 'concord'
|
6
3
|
require 'diff/lcs'
|
7
4
|
require 'diff/lcs/hunk'
|
8
|
-
require 'mprelude'
|
9
5
|
require 'optparse'
|
10
6
|
require 'parser/current'
|
11
|
-
require 'procto'
|
12
7
|
require 'set'
|
13
8
|
|
9
|
+
require 'unparser/equalizer'
|
10
|
+
require 'unparser/adamantium'
|
11
|
+
require 'unparser/adamantium/method_builder'
|
12
|
+
require 'unparser/abstract_type'
|
13
|
+
|
14
|
+
require 'unparser/concord'
|
15
|
+
require 'unparser/either'
|
16
|
+
require 'unparser/anima'
|
17
|
+
require 'unparser/anima/attribute'
|
18
|
+
require 'unparser/anima/error'
|
19
|
+
|
14
20
|
# Library namespace
|
15
21
|
module Unparser
|
16
22
|
# Unparser specific AST builder defaulting to modern AST format
|
@@ -27,7 +33,18 @@ module Unparser
|
|
27
33
|
EMPTY_STRING = ''.freeze
|
28
34
|
EMPTY_ARRAY = [].freeze
|
29
35
|
|
30
|
-
private_constant(*constants(false))
|
36
|
+
private_constant(*constants(false) - %i[Adamantium AbstractType Anima Concord Either Equalizer Memoizable])
|
37
|
+
|
38
|
+
# Error raised when unparser encounters an invalid AST
|
39
|
+
class InvalidNodeError < RuntimeError
|
40
|
+
attr_reader :node
|
41
|
+
|
42
|
+
def initialize(message, node)
|
43
|
+
super(message)
|
44
|
+
@node = node
|
45
|
+
freeze
|
46
|
+
end
|
47
|
+
end
|
31
48
|
|
32
49
|
# Unparse an AST (and, optionally, comments) into a string
|
33
50
|
#
|
@@ -36,8 +53,10 @@ module Unparser
|
|
36
53
|
#
|
37
54
|
# @return [String]
|
38
55
|
#
|
39
|
-
# @
|
56
|
+
# @raise InvalidNodeError
|
57
|
+
# if the node passed is invalid
|
40
58
|
#
|
59
|
+
# @api public
|
41
60
|
def self.unparse(node, comment_array = [])
|
42
61
|
return '' if node.nil?
|
43
62
|
|
@@ -61,9 +80,9 @@ module Unparser
|
|
61
80
|
validation = Validation.from_string(generated)
|
62
81
|
|
63
82
|
if validation.success?
|
64
|
-
|
83
|
+
Either::Right.new(generated)
|
65
84
|
else
|
66
|
-
|
85
|
+
Either::Left.new(validation)
|
67
86
|
end
|
68
87
|
end
|
69
88
|
|
@@ -75,8 +94,7 @@ module Unparser
|
|
75
94
|
#
|
76
95
|
# @return [Either<Exception, String>]
|
77
96
|
def self.unparse_either(node)
|
78
|
-
|
79
|
-
.wrap_error(Exception) { unparse(node) }
|
97
|
+
Either.wrap_error(Exception) { unparse(node) }
|
80
98
|
end
|
81
99
|
|
82
100
|
# Parse string into AST
|
@@ -92,9 +110,9 @@ module Unparser
|
|
92
110
|
#
|
93
111
|
# @param [String] source
|
94
112
|
#
|
95
|
-
# @return [
|
113
|
+
# @return [Either<Parser::SyntaxError, (Parser::ASTNode, nil)>]
|
96
114
|
def self.parse_either(source)
|
97
|
-
|
115
|
+
Either.wrap_error(Parser::SyntaxError) do
|
98
116
|
parser.parse(buffer(source))
|
99
117
|
end
|
100
118
|
end
|
@@ -117,21 +135,10 @@ module Unparser
|
|
117
135
|
Parser::CurrentRuby.new(Builder.new).tap do |parser|
|
118
136
|
parser.diagnostics.tap do |diagnostics|
|
119
137
|
diagnostics.all_errors_are_fatal = true
|
120
|
-
diagnostics.consumer = method(:consume_diagnostic)
|
121
138
|
end
|
122
139
|
end
|
123
140
|
end
|
124
141
|
|
125
|
-
# Consume diagnostic
|
126
|
-
#
|
127
|
-
# @param [Parser::Diagnostic] diagnostic
|
128
|
-
#
|
129
|
-
# @return [undefined]
|
130
|
-
def self.consume_diagnostic(diagnostic)
|
131
|
-
Kernel.warn(diagnostic.render)
|
132
|
-
end
|
133
|
-
private_class_method :consume_diagnostic
|
134
|
-
|
135
142
|
# Construct a parser buffer from string
|
136
143
|
#
|
137
144
|
# @param [String] source
|
@@ -209,6 +216,9 @@ require 'unparser/emitter/undef'
|
|
209
216
|
require 'unparser/emitter/variable'
|
210
217
|
require 'unparser/emitter/xstr'
|
211
218
|
require 'unparser/emitter/yield'
|
219
|
+
require 'unparser/emitter/kwargs'
|
220
|
+
require 'unparser/emitter/pair'
|
221
|
+
require 'unparser/emitter/match_pattern'
|
212
222
|
require 'unparser/writer'
|
213
223
|
require 'unparser/writer/binary'
|
214
224
|
require 'unparser/writer/dynamic_string'
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unparser
|
4
|
+
# Module to allow class and methods to be abstract
|
5
|
+
#
|
6
|
+
# Original code before vendoring and reduction from: https://github.com/dkubb/abstract_type.
|
7
|
+
module AbstractType
|
8
|
+
|
9
|
+
# Hook called when module is included
|
10
|
+
#
|
11
|
+
# @param [Module] descendant
|
12
|
+
# the module or class including AbstractType
|
13
|
+
#
|
14
|
+
# @return [undefined]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
def self.included(descendant)
|
18
|
+
super
|
19
|
+
create_new_method(descendant)
|
20
|
+
descendant.extend(AbstractMethodDeclarations)
|
21
|
+
end
|
22
|
+
|
23
|
+
private_class_method :included
|
24
|
+
|
25
|
+
# Define the new method on the abstract type
|
26
|
+
#
|
27
|
+
# Ensures that the instance cannot be of the abstract type
|
28
|
+
# and must be a descendant.
|
29
|
+
#
|
30
|
+
# @param [Class] abstract_class
|
31
|
+
#
|
32
|
+
# @return [undefined]
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
def self.create_new_method(abstract_class)
|
36
|
+
abstract_class.define_singleton_method(:new) do |*args, &block|
|
37
|
+
if equal?(abstract_class)
|
38
|
+
fail NotImplementedError, "#{self} is an abstract type"
|
39
|
+
else
|
40
|
+
super(*args, &block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private_class_method :create_new_method
|
46
|
+
|
47
|
+
module AbstractMethodDeclarations
|
48
|
+
|
49
|
+
# Create abstract instance methods
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# class Foo
|
53
|
+
# include AbstractType
|
54
|
+
#
|
55
|
+
# # Create an abstract instance method
|
56
|
+
# abstract_method :some_method
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# @param [Array<#to_s>] names
|
60
|
+
#
|
61
|
+
# @return [self]
|
62
|
+
#
|
63
|
+
# @api public
|
64
|
+
def abstract_method(*names)
|
65
|
+
names.each(&method(:create_abstract_instance_method))
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
# Create abstract singleton methods
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
# class Foo
|
73
|
+
# include AbstractType
|
74
|
+
#
|
75
|
+
# # Create an abstract instance method
|
76
|
+
# abstract_singleton_method :some_method
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# @param [Array<#to_s>] names
|
80
|
+
#
|
81
|
+
# @return [self]
|
82
|
+
#
|
83
|
+
# @api private
|
84
|
+
def abstract_singleton_method(*names)
|
85
|
+
names.each(&method(:create_abstract_singleton_method))
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Create abstract singleton method
|
92
|
+
#
|
93
|
+
# @param [#to_s] name
|
94
|
+
# the name of the method to create
|
95
|
+
#
|
96
|
+
# @return [undefined]
|
97
|
+
#
|
98
|
+
# @api private
|
99
|
+
def create_abstract_singleton_method(name)
|
100
|
+
define_singleton_method(name) do |*|
|
101
|
+
fail NotImplementedError, "#{self}.#{name} is not implemented"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Create abstract instance method
|
106
|
+
#
|
107
|
+
# @param [#to_s] name
|
108
|
+
# the name of the method to create
|
109
|
+
#
|
110
|
+
# @return [undefined]
|
111
|
+
#
|
112
|
+
# @api private
|
113
|
+
def create_abstract_instance_method(name)
|
114
|
+
define_method(name) do |*|
|
115
|
+
fail NotImplementedError, "#{self.class}##{name} is not implemented"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end # AbstractMethodDeclarations
|
120
|
+
end # AbstractType
|
121
|
+
end # Unparser
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unparser
|
4
|
+
# Allows objects to be made immutable
|
5
|
+
#
|
6
|
+
# Original code before vendoring and reduction from: https://github.com/dkubb/adamantium.
|
7
|
+
module Adamantium
|
8
|
+
module InstanceMethods
|
9
|
+
# A noop #dup for immutable objects
|
10
|
+
#
|
11
|
+
# @return [self]
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
def dup
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
# Freeze the object
|
19
|
+
#
|
20
|
+
# @return [Object]
|
21
|
+
#
|
22
|
+
# @api public
|
23
|
+
def freeze
|
24
|
+
memoized_method_cache
|
25
|
+
super()
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def memoized_method_cache
|
31
|
+
@memoized_method_cache ||= Memory.new({})
|
32
|
+
end
|
33
|
+
|
34
|
+
end # InstanceMethods
|
35
|
+
|
36
|
+
# Storage for memoized methods
|
37
|
+
class Memory
|
38
|
+
|
39
|
+
# Initialize the memory storage for memoized methods
|
40
|
+
#
|
41
|
+
# @return [undefined]
|
42
|
+
#
|
43
|
+
# @api private
|
44
|
+
def initialize(values)
|
45
|
+
@values = values
|
46
|
+
@monitor = Monitor.new
|
47
|
+
freeze
|
48
|
+
end
|
49
|
+
|
50
|
+
# Fetch the value from memory, or evaluate if it does not exist
|
51
|
+
#
|
52
|
+
# @param [Symbol] name
|
53
|
+
#
|
54
|
+
# @yieldreturn [Object]
|
55
|
+
# the value to memoize
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
def fetch(name)
|
59
|
+
@values.fetch(name) do # check for the key
|
60
|
+
@monitor.synchronize do # acquire a lock if the key is not found
|
61
|
+
@values.fetch(name) do # recheck under lock
|
62
|
+
@values[name] = yield # set the value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end # Memory
|
68
|
+
|
69
|
+
# Methods mixed in to adamantium classes
|
70
|
+
module ClassMethods
|
71
|
+
|
72
|
+
# Instantiate a new frozen object
|
73
|
+
#
|
74
|
+
# @return [Object]
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
def new(*)
|
78
|
+
super.freeze
|
79
|
+
end
|
80
|
+
|
81
|
+
end # ClassMethods
|
82
|
+
|
83
|
+
# Methods mixed in to adamantium modules
|
84
|
+
module ModuleMethods
|
85
|
+
|
86
|
+
# Memoize a list of methods
|
87
|
+
#
|
88
|
+
# @param [Array<#to_s>] methods
|
89
|
+
# a list of methods to memoize
|
90
|
+
#
|
91
|
+
# @return [self]
|
92
|
+
#
|
93
|
+
# @api public
|
94
|
+
def memoize(*methods)
|
95
|
+
methods.each(&method(:memoize_method))
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
# Test if method is memoized
|
100
|
+
#
|
101
|
+
# @param [Symbol] name
|
102
|
+
#
|
103
|
+
# @return [Bool]
|
104
|
+
def memoized?(method_name)
|
105
|
+
memoized_methods.key?(method_name)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Return unmemoized instance method
|
109
|
+
#
|
110
|
+
# @param [Symbol] name
|
111
|
+
#
|
112
|
+
# @return [UnboundMethod]
|
113
|
+
# the memoized method
|
114
|
+
#
|
115
|
+
# @raise [NameError]
|
116
|
+
# raised if the method is unknown
|
117
|
+
#
|
118
|
+
# @api public
|
119
|
+
def unmemoized_instance_method(method_name)
|
120
|
+
memoized_methods.fetch(method_name) do
|
121
|
+
fail ArgumentError, "##{method_name} is not memoized"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def memoize_method(method_name)
|
128
|
+
if memoized_methods.key?(method_name)
|
129
|
+
fail ArgumentError, "##{method_name} is already memoized"
|
130
|
+
end
|
131
|
+
|
132
|
+
memoized_methods[method_name] = MethodBuilder.new(self, method_name).call
|
133
|
+
end
|
134
|
+
|
135
|
+
def memoized_methods
|
136
|
+
@memoized_methods ||= {}
|
137
|
+
end
|
138
|
+
|
139
|
+
end # ModuleMethods
|
140
|
+
|
141
|
+
def self.included(descendant)
|
142
|
+
descendant.class_eval do
|
143
|
+
include InstanceMethods
|
144
|
+
extend ModuleMethods
|
145
|
+
extend ClassMethods if instance_of?(Class)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
private_class_method :included
|
149
|
+
end # Adamantium
|
150
|
+
end # Unparser
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unparser
|
4
|
+
module Adamantium
|
5
|
+
# Build the memoized method
|
6
|
+
class MethodBuilder
|
7
|
+
|
8
|
+
# Raised when the method arity is invalid
|
9
|
+
class InvalidArityError < ArgumentError
|
10
|
+
|
11
|
+
# Initialize an invalid arity exception
|
12
|
+
#
|
13
|
+
# @param [Module] descendant
|
14
|
+
# @param [Symbol] method
|
15
|
+
# @param [Integer] arity
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
def initialize(descendant, method, arity)
|
19
|
+
super("Cannot memoize #{descendant}##{method}, its arity is #{arity}")
|
20
|
+
end
|
21
|
+
|
22
|
+
end # InvalidArityError
|
23
|
+
|
24
|
+
# Raised when a block is passed to a memoized method
|
25
|
+
class BlockNotAllowedError < ArgumentError
|
26
|
+
|
27
|
+
# Initialize a block not allowed exception
|
28
|
+
#
|
29
|
+
# @param [Module] descendant
|
30
|
+
# @param [Symbol] method
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
def initialize(descendant, method)
|
34
|
+
super("Cannot pass a block to #{descendant}##{method}, it is memoized")
|
35
|
+
end
|
36
|
+
|
37
|
+
end # BlockNotAllowedError
|
38
|
+
|
39
|
+
# Initialize an object to build a memoized method
|
40
|
+
#
|
41
|
+
# @param [Module] descendant
|
42
|
+
# @param [Symbol] method_name
|
43
|
+
#
|
44
|
+
# @return [undefined]
|
45
|
+
#
|
46
|
+
# @api private
|
47
|
+
def initialize(descendant, method_name)
|
48
|
+
@descendant = descendant
|
49
|
+
@method_name = method_name
|
50
|
+
@original_visibility = visibility
|
51
|
+
@original_method = @descendant.instance_method(@method_name)
|
52
|
+
assert_arity(@original_method.arity)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Build a new memoized method
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
# method_builder.call # => creates new method
|
59
|
+
#
|
60
|
+
# @return [UnboundMethod]
|
61
|
+
#
|
62
|
+
# @api public
|
63
|
+
def call
|
64
|
+
remove_original_method
|
65
|
+
create_memoized_method
|
66
|
+
set_method_visibility
|
67
|
+
@original_method
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def assert_arity(arity)
|
73
|
+
if arity.nonzero?
|
74
|
+
fail InvalidArityError.new(@descendant, @method_name, arity)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def remove_original_method
|
79
|
+
name = @method_name
|
80
|
+
@descendant.module_eval { undef_method(name) }
|
81
|
+
end
|
82
|
+
|
83
|
+
def create_memoized_method
|
84
|
+
name = @method_name
|
85
|
+
method = @original_method
|
86
|
+
@descendant.module_eval do
|
87
|
+
define_method(name) do |&block|
|
88
|
+
fail BlockNotAllowedError.new(self.class, name) if block
|
89
|
+
|
90
|
+
memoized_method_cache.fetch(name) do
|
91
|
+
method.bind(self).call.freeze
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def set_method_visibility
|
98
|
+
@descendant.__send__(@original_visibility, @method_name)
|
99
|
+
end
|
100
|
+
|
101
|
+
def visibility
|
102
|
+
if @descendant.private_method_defined?(@method_name) then :private
|
103
|
+
elsif @descendant.protected_method_defined?(@method_name) then :protected
|
104
|
+
else :public
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end # MethodBuilder
|
109
|
+
end # Adamantium
|
110
|
+
end # Unparser
|