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
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unparser
|
4
|
+
# Original code before vendoring and reduction from: https://github.com/mbj/anima.
|
5
|
+
class Anima < Module
|
6
|
+
include Adamantium, Equalizer.new(:attributes)
|
7
|
+
|
8
|
+
# Return names
|
9
|
+
#
|
10
|
+
# @return [AttributeSet]
|
11
|
+
attr_reader :attributes
|
12
|
+
|
13
|
+
# Initialize object
|
14
|
+
#
|
15
|
+
# @return [undefined]
|
16
|
+
#
|
17
|
+
# rubocop:disable Lint/MissingSuper
|
18
|
+
def initialize(*names)
|
19
|
+
@attributes = names.uniq.map(&Attribute.public_method(:new)).freeze
|
20
|
+
end
|
21
|
+
# rubocop:enable Lint/MissingSuper
|
22
|
+
|
23
|
+
# Return new anima with attributes added
|
24
|
+
#
|
25
|
+
# @return [Anima]
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# anima = Anima.new(:foo)
|
29
|
+
# anima.add(:bar) # equals Anima.new(:foo, :bar)
|
30
|
+
#
|
31
|
+
def add(*names)
|
32
|
+
new(attribute_names + names)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return new anima with attributes removed
|
36
|
+
#
|
37
|
+
# @return [Anima]
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# anima = Anima.new(:foo, :bar)
|
41
|
+
# anima.remove(:bar) # equals Anima.new(:foo)
|
42
|
+
#
|
43
|
+
def remove(*names)
|
44
|
+
new(attribute_names - names)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return attributes hash for instance
|
48
|
+
#
|
49
|
+
# @param [Object] object
|
50
|
+
#
|
51
|
+
# @return [Hash]
|
52
|
+
def attributes_hash(object)
|
53
|
+
attributes.each_with_object({}) do |attribute, attributes_hash|
|
54
|
+
attributes_hash[attribute.name] = attribute.get(object)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Return attribute names
|
59
|
+
#
|
60
|
+
# @return [Enumerable<Symbol>]
|
61
|
+
def attribute_names
|
62
|
+
attributes.map(&:name)
|
63
|
+
end
|
64
|
+
memoize :attribute_names
|
65
|
+
|
66
|
+
# Initialize instance
|
67
|
+
#
|
68
|
+
# @param [Object] object
|
69
|
+
#
|
70
|
+
# @param [Hash] attribute_hash
|
71
|
+
#
|
72
|
+
# @return [self]
|
73
|
+
def initialize_instance(object, attribute_hash)
|
74
|
+
assert_known_attributes(object.class, attribute_hash)
|
75
|
+
attributes.each do |attribute|
|
76
|
+
attribute.load(object, attribute_hash)
|
77
|
+
end
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
# Static instance methods for anima infected classes
|
82
|
+
module InstanceMethods
|
83
|
+
# Initialize an anima infected object
|
84
|
+
#
|
85
|
+
# @param [#to_h] attributes
|
86
|
+
# a hash that matches anima defined attributes
|
87
|
+
#
|
88
|
+
# @return [undefined]
|
89
|
+
#
|
90
|
+
# rubocop:disable Lint/MissingSuper
|
91
|
+
def initialize(attributes)
|
92
|
+
self.class.anima.initialize_instance(self, attributes)
|
93
|
+
end
|
94
|
+
# rubocop:enable Lint/MissingSuper
|
95
|
+
|
96
|
+
# Return a hash representation of an anima infected object
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# anima.to_h # => { :foo => : bar }
|
100
|
+
#
|
101
|
+
# @return [Hash]
|
102
|
+
#
|
103
|
+
# @api public
|
104
|
+
def to_h
|
105
|
+
self.class.anima.attributes_hash(self)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Return updated instance
|
109
|
+
#
|
110
|
+
# @example
|
111
|
+
# klass = Class.new do
|
112
|
+
# include Anima.new(:foo, :bar)
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
# foo = klass.new(:foo => 1, :bar => 2)
|
116
|
+
# updated = foo.with(:foo => 3)
|
117
|
+
# updated.foo # => 3
|
118
|
+
# updated.bar # => 2
|
119
|
+
#
|
120
|
+
# @param [Hash] attributes
|
121
|
+
#
|
122
|
+
# @return [Anima]
|
123
|
+
#
|
124
|
+
# @api public
|
125
|
+
def with(attributes)
|
126
|
+
self.class.new(to_h.update(attributes))
|
127
|
+
end
|
128
|
+
end # InstanceMethods
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
# Infect the instance with anima
|
133
|
+
#
|
134
|
+
# @param [Class, Module] scope
|
135
|
+
#
|
136
|
+
# @return [undefined]
|
137
|
+
def included(descendant)
|
138
|
+
descendant.instance_exec(self, attribute_names) do |anima, names|
|
139
|
+
# Define anima method
|
140
|
+
define_singleton_method(:anima) { anima }
|
141
|
+
|
142
|
+
# Define instance methods
|
143
|
+
include InstanceMethods
|
144
|
+
|
145
|
+
# Define attribute readers
|
146
|
+
attr_reader(*names)
|
147
|
+
|
148
|
+
# Define equalizer
|
149
|
+
include Equalizer.new(*names)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Fail unless keys in +attribute_hash+ matches #attribute_names
|
154
|
+
#
|
155
|
+
# @param [Class] klass
|
156
|
+
# the class being initialized
|
157
|
+
#
|
158
|
+
# @param [Hash] attribute_hash
|
159
|
+
# the attributes to initialize +object+ with
|
160
|
+
#
|
161
|
+
# @return [undefined]
|
162
|
+
#
|
163
|
+
# @raise [Error]
|
164
|
+
def assert_known_attributes(klass, attribute_hash)
|
165
|
+
keys = attribute_hash.keys
|
166
|
+
|
167
|
+
unknown = keys - attribute_names
|
168
|
+
missing = attribute_names - keys
|
169
|
+
|
170
|
+
unless unknown.empty? && missing.empty?
|
171
|
+
fail Error.new(klass, missing, unknown)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Return new instance
|
176
|
+
#
|
177
|
+
# @param [Enumerable<Symbol>] attributes
|
178
|
+
#
|
179
|
+
# @return [Anima]
|
180
|
+
def new(attributes)
|
181
|
+
self.class.new(*attributes)
|
182
|
+
end
|
183
|
+
end # Anima
|
184
|
+
end # Unparser
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unparser
|
4
|
+
class Anima
|
5
|
+
# An attribute
|
6
|
+
class Attribute
|
7
|
+
include Adamantium, Equalizer.new(:name)
|
8
|
+
|
9
|
+
# Initialize attribute
|
10
|
+
#
|
11
|
+
# @param [Symbol] name
|
12
|
+
def initialize(name)
|
13
|
+
@name = name
|
14
|
+
@instance_variable_name = :"@#{name}"
|
15
|
+
end
|
16
|
+
|
17
|
+
# Return attribute name
|
18
|
+
#
|
19
|
+
# @return [Symbol]
|
20
|
+
attr_reader :name
|
21
|
+
|
22
|
+
# Return instance variable name
|
23
|
+
#
|
24
|
+
# @return [Symbol]
|
25
|
+
attr_reader :instance_variable_name
|
26
|
+
|
27
|
+
# Load attribute
|
28
|
+
#
|
29
|
+
# @param [Object] object
|
30
|
+
# @param [Hash] attributes
|
31
|
+
#
|
32
|
+
# @return [self]
|
33
|
+
def load(object, attributes)
|
34
|
+
set(object, attributes.fetch(name))
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get attribute value from object
|
38
|
+
#
|
39
|
+
# @param [Object] object
|
40
|
+
#
|
41
|
+
# @return [Object]
|
42
|
+
def get(object)
|
43
|
+
object.public_send(name)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Set attribute value in object
|
47
|
+
#
|
48
|
+
# @param [Object] object
|
49
|
+
# @param [Object] value
|
50
|
+
#
|
51
|
+
# @return [self]
|
52
|
+
def set(object, value)
|
53
|
+
object.instance_variable_set(instance_variable_name, value)
|
54
|
+
|
55
|
+
self
|
56
|
+
end
|
57
|
+
end # Attribute
|
58
|
+
end # Anima
|
59
|
+
end # Unparser
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unparser
|
4
|
+
class Anima
|
5
|
+
# Abstract base class for anima errors
|
6
|
+
class Error < RuntimeError
|
7
|
+
FORMAT = '%s attributes missing: %s, unknown: %s'.freeze
|
8
|
+
private_constant(*constants(false))
|
9
|
+
|
10
|
+
# Initialize object
|
11
|
+
#
|
12
|
+
# @param [Class] klass
|
13
|
+
# the class being initialized
|
14
|
+
# @param [Enumerable<Symbol>] missing
|
15
|
+
# @param [Enumerable<Symbol>] unknown
|
16
|
+
#
|
17
|
+
# @return [undefined]
|
18
|
+
def initialize(klass, missing, unknown)
|
19
|
+
super(format(FORMAT, klass, missing, unknown))
|
20
|
+
end
|
21
|
+
end # Error
|
22
|
+
end # Anima
|
23
|
+
end # Unparser
|
data/lib/unparser/ast.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
module Unparser
|
4
4
|
# Namespace for AST processing tools
|
5
5
|
module AST
|
6
|
-
|
7
6
|
FIRST_CHILD = ->(node) { node.children.first }.freeze
|
8
7
|
TAUTOLOGY = ->(_node) { true }.freeze
|
9
8
|
|
@@ -79,7 +78,7 @@ module Unparser
|
|
79
78
|
|
80
79
|
# AST enumerator
|
81
80
|
class Enumerator
|
82
|
-
include Adamantium
|
81
|
+
include Adamantium, Concord.new(:node, :controller), Enumerable
|
83
82
|
|
84
83
|
# Return new instance
|
85
84
|
#
|
data/lib/unparser/color.rb
CHANGED
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unparser
|
4
|
+
# A mixin to define a composition
|
5
|
+
#
|
6
|
+
# Original code before vendoring and reduction from: https://github.com/mbj/concord.
|
7
|
+
class Concord < Module
|
8
|
+
include Adamantium, Equalizer.new(:names)
|
9
|
+
|
10
|
+
# The maximum number of objects the hosting class is composed of
|
11
|
+
MAX_NR_OF_OBJECTS = 3
|
12
|
+
|
13
|
+
# Return names
|
14
|
+
#
|
15
|
+
# @return [Enumerable<Symbol>]
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
#
|
19
|
+
attr_reader :names
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Initialize object
|
24
|
+
#
|
25
|
+
# @return [undefined]
|
26
|
+
#
|
27
|
+
# @api private
|
28
|
+
#
|
29
|
+
# rubocop:disable Lint/MissingSuper
|
30
|
+
def initialize(*names)
|
31
|
+
if names.length > MAX_NR_OF_OBJECTS
|
32
|
+
fail "Composition of more than #{MAX_NR_OF_OBJECTS} objects is not allowed"
|
33
|
+
end
|
34
|
+
|
35
|
+
@names = names
|
36
|
+
define_initialize
|
37
|
+
define_readers
|
38
|
+
define_equalizer
|
39
|
+
end
|
40
|
+
# rubocop:enable Lint/MissingSuper
|
41
|
+
|
42
|
+
# Define equalizer
|
43
|
+
#
|
44
|
+
# @return [undefined]
|
45
|
+
#
|
46
|
+
# @api private
|
47
|
+
#
|
48
|
+
def define_equalizer
|
49
|
+
include(Equalizer.new(*names))
|
50
|
+
end
|
51
|
+
|
52
|
+
# Define readers
|
53
|
+
#
|
54
|
+
# @return [undefined]
|
55
|
+
#
|
56
|
+
# @api private
|
57
|
+
#
|
58
|
+
def define_readers
|
59
|
+
attribute_names = names
|
60
|
+
attr_reader(*attribute_names)
|
61
|
+
|
62
|
+
protected(*attribute_names) if attribute_names.any?
|
63
|
+
end
|
64
|
+
|
65
|
+
# Define initialize method
|
66
|
+
#
|
67
|
+
# @return [undefined]
|
68
|
+
#
|
69
|
+
# @api private
|
70
|
+
#
|
71
|
+
#
|
72
|
+
def define_initialize
|
73
|
+
ivars = instance_variable_names
|
74
|
+
size = names.size
|
75
|
+
|
76
|
+
define_method :initialize do |*args|
|
77
|
+
args_size = args.size
|
78
|
+
unless args_size.equal?(size)
|
79
|
+
fail ArgumentError, "wrong number of arguments (#{args_size} for #{size})"
|
80
|
+
end
|
81
|
+
|
82
|
+
ivars.zip(args) { |ivar, arg| instance_variable_set(ivar, arg) }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Return instance variable names
|
87
|
+
#
|
88
|
+
# @return [String]
|
89
|
+
#
|
90
|
+
# @api private
|
91
|
+
#
|
92
|
+
def instance_variable_names
|
93
|
+
names.map { |name| "@#{name}" }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Mixin for public attribute readers
|
97
|
+
class Public < self
|
98
|
+
|
99
|
+
# Hook called when module is included
|
100
|
+
#
|
101
|
+
# @param [Class,Module] descendant
|
102
|
+
#
|
103
|
+
# @return [undefined]
|
104
|
+
#
|
105
|
+
# @api private
|
106
|
+
#
|
107
|
+
def included(descendant)
|
108
|
+
names.each do |name|
|
109
|
+
descendant.__send__(:public, name)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end # Public
|
113
|
+
end # Concord
|
114
|
+
end # Unparser
|
data/lib/unparser/diff.rb
CHANGED
@@ -0,0 +1,153 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unparser
|
4
|
+
module RequireBlock
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
# Raise error unless block is provided
|
9
|
+
#
|
10
|
+
# @raise [MissingBlockError]
|
11
|
+
# if no block is given
|
12
|
+
#
|
13
|
+
# @return [self]
|
14
|
+
def require_block
|
15
|
+
fail LocalJumpError unless block_given?
|
16
|
+
|
17
|
+
self
|
18
|
+
end
|
19
|
+
end # RequireBLock
|
20
|
+
|
21
|
+
class Either
|
22
|
+
include(
|
23
|
+
Adamantium,
|
24
|
+
Concord.new(:value),
|
25
|
+
RequireBlock
|
26
|
+
)
|
27
|
+
|
28
|
+
# Execute block and wrap error in left
|
29
|
+
#
|
30
|
+
# @param [Class<Exception>] exception
|
31
|
+
#
|
32
|
+
# @return [Either<Exception, Object>]
|
33
|
+
def self.wrap_error(*exceptions)
|
34
|
+
Right.new(yield)
|
35
|
+
rescue *exceptions => error
|
36
|
+
Left.new(error)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Test for left constructor
|
40
|
+
#
|
41
|
+
# @return [Boolean]
|
42
|
+
def left?
|
43
|
+
instance_of?(Left)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Test for right constructor
|
47
|
+
#
|
48
|
+
# @return [Boolean]
|
49
|
+
def right?
|
50
|
+
instance_of?(Right)
|
51
|
+
end
|
52
|
+
|
53
|
+
class Left < self
|
54
|
+
# Evaluate functor block
|
55
|
+
#
|
56
|
+
# @return [Either::Left<Object>]
|
57
|
+
def fmap(&block)
|
58
|
+
require_block(&block)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Evaluate applicative block
|
62
|
+
#
|
63
|
+
# @return [Either::Left<Object>]
|
64
|
+
def bind(&block)
|
65
|
+
require_block(&block)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Unwrap value from left
|
69
|
+
#
|
70
|
+
# @return [Object]
|
71
|
+
def from_left
|
72
|
+
value
|
73
|
+
end
|
74
|
+
|
75
|
+
# Unwrap value from right
|
76
|
+
#
|
77
|
+
# @return [Object]
|
78
|
+
#
|
79
|
+
def from_right
|
80
|
+
if block_given?
|
81
|
+
yield(value)
|
82
|
+
else
|
83
|
+
fail "Expected right value, got #{inspect}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Map over left value
|
88
|
+
#
|
89
|
+
# @return [Either::Right<Object>]
|
90
|
+
def lmap
|
91
|
+
Left.new(yield(value))
|
92
|
+
end
|
93
|
+
|
94
|
+
# Evaluate left side of branch
|
95
|
+
#
|
96
|
+
# @param [#call] left
|
97
|
+
# @param [#call] _right
|
98
|
+
def either(left, _right)
|
99
|
+
left.call(value)
|
100
|
+
end
|
101
|
+
end # Left
|
102
|
+
|
103
|
+
class Right < self
|
104
|
+
# Evaluate functor block
|
105
|
+
#
|
106
|
+
# @return [Either::Right<Object>]
|
107
|
+
def fmap
|
108
|
+
Right.new(yield(value))
|
109
|
+
end
|
110
|
+
|
111
|
+
# Evaluate applicative block
|
112
|
+
#
|
113
|
+
# @return [Either<Object>]
|
114
|
+
def bind
|
115
|
+
yield(value)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Unwrap value from left
|
119
|
+
#
|
120
|
+
# @return [Object]
|
121
|
+
#
|
122
|
+
def from_left
|
123
|
+
if block_given?
|
124
|
+
yield(value)
|
125
|
+
else
|
126
|
+
fail "Expected left value, got #{inspect}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Unwrap value from right
|
131
|
+
#
|
132
|
+
# @return [Object]
|
133
|
+
def from_right
|
134
|
+
value
|
135
|
+
end
|
136
|
+
|
137
|
+
# Map over left value
|
138
|
+
#
|
139
|
+
# @return [Either::Right<Object>]
|
140
|
+
def lmap(&block)
|
141
|
+
require_block(&block)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Evaluate right side of branch
|
145
|
+
#
|
146
|
+
# @param [#call] _left
|
147
|
+
# @param [#call] right
|
148
|
+
def either(_left, right)
|
149
|
+
right.call(value)
|
150
|
+
end
|
151
|
+
end # Right
|
152
|
+
end # Either
|
153
|
+
end # Unparser
|