unparser 0.5.3 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|