literal 0.1.0 → 0.2.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +100 -54
  3. data/lib/literal/data.rb +24 -11
  4. data/lib/literal/data_property.rb +16 -0
  5. data/lib/literal/data_structure.rb +60 -0
  6. data/lib/literal/enum.rb +176 -0
  7. data/lib/literal/errors/argument_error.rb +5 -0
  8. data/lib/literal/errors/error.rb +4 -0
  9. data/lib/literal/errors/type_error.rb +10 -0
  10. data/lib/literal/null.rb +9 -0
  11. data/lib/literal/object.rb +5 -0
  12. data/lib/literal/properties/data_schema.rb +9 -0
  13. data/lib/literal/properties/schema.rb +118 -0
  14. data/lib/literal/properties.rb +91 -0
  15. data/lib/literal/property.rb +196 -0
  16. data/lib/literal/struct.rb +8 -34
  17. data/lib/literal/types/any_type.rb +10 -3
  18. data/lib/literal/types/array_type.rb +10 -9
  19. data/lib/literal/types/boolean_type.rb +18 -1
  20. data/lib/literal/types/callable_type.rb +12 -0
  21. data/lib/literal/types/class_type.rb +10 -9
  22. data/lib/literal/types/constraint_type.rb +16 -0
  23. data/lib/literal/types/descendant_type.rb +13 -0
  24. data/lib/literal/types/enumerable_type.rb +10 -9
  25. data/lib/literal/types/falsy_type.rb +12 -0
  26. data/lib/literal/types/float_type.rb +7 -10
  27. data/lib/literal/types/frozen_type.rb +14 -0
  28. data/lib/literal/types/hash_type.rb +11 -10
  29. data/lib/literal/types/integer_type.rb +7 -10
  30. data/lib/literal/types/interface_type.rb +11 -9
  31. data/lib/literal/types/intersection_type.rb +20 -0
  32. data/lib/literal/types/json_data_type.rb +21 -0
  33. data/lib/literal/types/lambda_type.rb +12 -0
  34. data/lib/literal/types/map_type.rb +16 -0
  35. data/lib/literal/types/never_type.rb +12 -0
  36. data/lib/literal/types/nilable_type.rb +14 -0
  37. data/lib/literal/types/not_type.rb +14 -0
  38. data/lib/literal/types/procable_type.rb +12 -0
  39. data/lib/literal/types/range_type.rb +20 -0
  40. data/lib/literal/types/set_type.rb +10 -9
  41. data/lib/literal/types/string_type.rb +10 -0
  42. data/lib/literal/types/symbol_type.rb +10 -0
  43. data/lib/literal/types/truthy_type.rb +12 -0
  44. data/lib/literal/types/tuple_type.rb +12 -9
  45. data/lib/literal/types/union_type.rb +43 -9
  46. data/lib/literal/types/void_type.rb +12 -0
  47. data/lib/literal/types.rb +195 -54
  48. data/lib/literal/version.rb +1 -1
  49. data/lib/literal.rb +28 -10
  50. data/lib/literal.test.rb +5 -0
  51. metadata +41 -19
  52. data/CHANGELOG.md +0 -5
  53. data/CODE_OF_CONDUCT.md +0 -84
  54. data/Gemfile +0 -9
  55. data/Gemfile.lock +0 -29
  56. data/Rakefile +0 -12
  57. data/lib/literal/attributes.rb +0 -33
  58. data/lib/literal/initializer.rb +0 -11
  59. data/lib/literal/model.rb +0 -22
  60. data/literal.gemspec +0 -37
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 02ab6065a85602e1f60d915cb11cf8e0505881894ec9df310f08fc9194124a77
4
- data.tar.gz: d86ff5c802deb2896486fa016da1f851af1296be511b79f5eab779a6e7bcb169
3
+ metadata.gz: 6ab1d4d21286c3cc2a094362e0f1f6a94856738c7ba9a93d285607bb57ce49b5
4
+ data.tar.gz: 27c2f4fe6ecbb00fcaeb5faf5b4d13849c3ef8dcc6eec817775f55648fac4847
5
5
  SHA512:
6
- metadata.gz: de1ddb15cb9172712bc0534ac4c4c09aeb8d224496e8dcfe913cb03a1f3d4496b7ceee128e0eda7f94ada97dfa16160acafbae130165e7662a6714a6e25c2e67
7
- data.tar.gz: 59a8bebc8c29ca4b62ba2c0adb6acc03037f92589465012fcbb8edafbe3d8acb36526439c6acad3346f2e4f9210c3b33902a03395b2e1ddf7221dc3cd5d46f57
6
+ metadata.gz: ef894ddc2c2ee8ca82175d3f833ca0a05f479adf164766c681c4bc165a21840f8431da8c4ee6d4d971b10bf4f37427af40914795f2bdd14855927f818633abb7
7
+ data.tar.gz: 806aa654d1670c8842dae039c4e0845dbd1ea304bfabb634aefc0ea79b380889d8e249b3348274d11cc7ac01b173e3c9b468d13d4c44f321ceaa8ac83d5b11ec
data/README.md CHANGED
@@ -1,112 +1,158 @@
1
- # Literal
1
+ # A Literal Ruby Gem [WIP]
2
2
 
3
- ## Basic Usage
3
+ ## Types
4
4
 
5
- ### Mixin
5
+ Literal uses Ruby-native types. Any method that responds to `===(value)` is considered a type. Note, this is how Ruby’s case statements and pattern matching work. It’s also how `Array#any?` and `Array#all?` work. Essentially all Ruby objects are types and just work the way you’d expect them to. A few examples:
6
6
 
7
- ```ruby
8
- class User
9
- include Literal
7
+ - On a `Range`, `===(value)` checks if the value is within the range.
8
+ - On a `Regexp`, `===(value)` checks if the value matches the pattern.
9
+ - On a `Class`, `===(value)` checks if the value is an instance of the class.
10
+ - On a `Proc`, `===(value)` calls the proc with the value.
11
+ - On a `String`, `===(value)` checks if the value is equal to the string.
12
+ - On the class `String`, `===(value)` checks if the value is a string.
10
13
 
11
- attribute :name, String
12
- attribute :age, Integer
13
- end
14
- ```
14
+ Literal extends this idea with the concept of generics or _parameterised_ types. A generic is a method that returns an object that respond to `===(value)`.
15
15
 
16
- ### Struct
16
+ If we want to check that a given value is an `Array`, we could do this:
17
17
 
18
18
  ```ruby
19
- class Person < Literal::Struct
20
- attribute :name, String
21
- attribute :age, Integer
22
- end
19
+ Array === [1, 2, 3]
23
20
  ```
24
21
 
25
- ### Data
22
+ But what if we want to check that it’s an Array of Integers? Literal provides a library of special types that can be composed for this purpose. In this case, we can use the type `_Array(Integer)`.
26
23
 
27
24
  ```ruby
28
- class Person < Literal::Data
29
- attribute :name, String
30
- attribute :age, Integer
31
- end
25
+ _Array(Integer) === [1, 2, 3]
32
26
  ```
33
27
 
34
- ## Special Types
35
-
36
- ### Union
28
+ These special types are defined on the `Literal::Types` module. To access them in a class, you can `extend` this module. To access them on an instance, you can `include` the module. If you want to use them globally, you can `extend` the module at the root.
37
29
 
38
30
  ```ruby
39
- _Union(String, Symbol)
31
+ extend Literal::Types
40
32
  ```
41
33
 
42
- ### Boolean
34
+ This is recommended for applications, but not for libraries, as we don’t want to pollute the global namespace from library code.
43
35
 
44
- ```ruby
45
- _Boolean
46
- ```
36
+ `Literal::Properties`, `Literal::Object`, `Literal::Struct` and `Literal::Data` already extend `Literal::Types`, so you don’t need to extend `Literal::Types` yourself if you’re only using literal types for literal properties.
47
37
 
48
- ### Maybe
38
+ ## Properties
39
+
40
+ `Literal::Properties` is a mixin that allows you to define the structure of an object. Properties are defined using the `prop` method.
41
+
42
+ The first argument is the name of the property as a `Symbol`. The second argument is the _type_ of the property. Remember, the type can be any object that responds to `===(value)`.
43
+
44
+ The third argument is optional. You can set this to `:*`, `:**`, `:&`, or `:positional` to change the kind of initializer parameter.
49
45
 
50
46
  ```ruby
51
- _Maybe(String)
47
+ class Person
48
+ extend Literal::Properties
49
+
50
+ prop :name, String
51
+ prop :age, Integer
52
+ end
52
53
  ```
53
54
 
54
- ### Array
55
+ You can also use keyword arguments to define _readers_ and _writers_. These can be set to `false`, `:public`, `:protected`, or `:private` and default to `false`.
55
56
 
56
57
  ```ruby
57
- _Array(String)
58
+ class Person
59
+ extend Literal::Properties
60
+
61
+ prop :name, String, reader: :public
62
+ prop :age, Integer, writer: :protected
63
+ end
58
64
  ```
59
65
 
60
- ### Set
66
+ Properties are required by deafult. To make them optional, set the type to a that responds to `===(nil)` with `true`. `Literal::Types` provides a special types for this purpose. Let’s make the age optional by setting its type to a `_Nilable(Integer)`:
61
67
 
62
68
  ```ruby
63
- _Set(String)
69
+ class Person
70
+ extend Literal::Properties
71
+
72
+ prop :name, String
73
+ prop :age, _Nilable(Integer)
74
+ end
64
75
  ```
65
76
 
66
- ### Enumerable
77
+ Alternatively, you can give the property a default value. This default value must match the type of the property.
67
78
 
68
79
  ```ruby
69
- _Enumerable(String)
80
+ class Person
81
+ extend Literal::Properties
82
+
83
+ prop :name, String, default: "John Doe"
84
+ prop :age, _Nilable(Integer)
85
+ end
70
86
  ```
71
87
 
72
- ### Tuple
73
- An Enumerable containing exactly the specified types in order.
88
+ Note, the above example will fail unless you have frozen string literals enabled. (Which, honestly, you should.) Default values must be frozen. If you can’t use a frozen value, you can pass a proc instead.
74
89
 
75
90
  ```ruby
76
- _Tuple(String, Integer)
91
+ class Person
92
+ extend Literal::Properties
93
+
94
+ prop :name, String, default: -> { "John Doe" }
95
+ prop :age, _Nilable(Integer)
96
+ end
77
97
  ```
78
98
 
79
- ### Hash
99
+ The proc will be called to generate the default value.
100
+
101
+ You can also pass a block to the `prop` method. This block will be called with the value of the property when it’s set, which is useful for coercion.
80
102
 
81
103
  ```ruby
82
- _Hash(String, Integer)
104
+ class Person
105
+ extend Literal::Properties
106
+
107
+ prop :name, String
108
+ prop :age, Integer do |value|
109
+ value.to_i
110
+ end
111
+ end
83
112
  ```
84
113
 
85
- ### Interface
114
+ Coercion takes place prior to type-checking, so you can safely coerce a value to a different type in the block.
115
+
116
+ You can use properties that conflict with ruby keywords. Literal will handle everything for you automatically.
117
+
86
118
  ```ruby
87
- _Interface(:to_s)
119
+ class Person
120
+ extend Literal::Properties
121
+
122
+ prop :class, String, :positional
123
+ prop :end, Integer
124
+ end
88
125
  ```
89
126
 
90
- ### Class
127
+ If you’d prefer to subclass than extend a module, you can use the `Literal::Object` class instead. `Literal::Object` literally extends `Literal::Properties`.
91
128
 
92
129
  ```ruby
93
- _Class(RuntimeError)
130
+ class Person < Literal::Object
131
+ prop :name, String
132
+ prop :age, Integer
133
+ end
94
134
  ```
95
135
 
96
- ### Module
136
+ ## Structs
137
+
138
+ `Literal::Struct` is like `Literal::Object`, but it also provides a few extras.
139
+
140
+ Structs implement `==` so you can compare one struct to another. They also implement `hash`. Structs also have public _readers_ and _writers_ by default.
97
141
 
98
142
  ```ruby
99
- _Module(Enumerable)
143
+ class Person < Literal::Struct
144
+ prop :name, String
145
+ prop :age, Integer
146
+ end
100
147
  ```
101
148
 
102
- ### Integer
103
- You can of course just use `Integer` to specify an integer type. The special type `_Integer` allows you to limit that type with a range, while verifying that it's an integer and not something else that matches the range such as a float.
149
+ ## Data
104
150
 
105
- ```
106
- _Integer(18..)
107
- ```
151
+ `Literal::Data` is like `Literal::Struct`, but you can’t define _writers_. Additionally, objects are _frozen_ after initialization. Additionally any non-frozen properties are duplicated and frozen.
108
152
 
109
- You can use these types together.
110
153
  ```ruby
111
- _Maybe(Union(String, Symbol, Interface(:to_s), Interface(:to_str), Tuple(String, Symbol)))
154
+ class Person < Literal::Data
155
+ prop :name, String
156
+ prop :age, Integer
157
+ end
112
158
  ```
data/lib/literal/data.rb CHANGED
@@ -1,12 +1,25 @@
1
- class Literal::Data < Literal::Struct
2
- def initialize(...)
3
- super
4
- @attributes.each(&:freeze)
5
- @attributes.freeze
6
- freeze
7
- end
8
-
9
- def dup(**attributes)
10
- self.class.new(**@attributes.merge(attributes))
11
- end
1
+ # frozen_string_literal: true
2
+
3
+ class Literal::Data < Literal::DataStructure
4
+ class << self
5
+ def prop(name, type, kind = :keyword, reader: :public, predicate: false, default: nil)
6
+ super(name, type, kind, reader:, writer: false, predicate:, default:)
7
+ end
8
+
9
+ def literal_properties
10
+ return @literal_properties if defined?(@literal_properties)
11
+
12
+ if superclass.is_a?(Literal::Data)
13
+ @literal_properties = superclass.literal_properties.dup
14
+ else
15
+ @literal_properties = Literal::Properties::DataSchema.new
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def __literal_property_class__
22
+ Literal::DataProperty
23
+ end
24
+ end
12
25
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Literal::DataProperty < Literal::Property
4
+ def generate_initializer_assign_value(buffer = +"")
5
+ buffer <<
6
+ "@" <<
7
+ @name.name <<
8
+ " = " <<
9
+ escaped_name <<
10
+ ".frozen? ? " <<
11
+ escaped_name <<
12
+ " : " <<
13
+ escaped_name <<
14
+ ".dup.freeze\n"
15
+ end
16
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ class Literal::DataStructure
5
+ extend Literal::Properties
6
+
7
+ def self.from_pack(payload)
8
+ object = allocate
9
+ object.marshal_load(payload)
10
+ object
11
+ end
12
+
13
+ def ==(other)
14
+ if Literal::DataStructure === other
15
+ to_h == other.to_h
16
+ else
17
+ false
18
+ end
19
+ end
20
+
21
+ def hash
22
+ [self.class, to_h].hash
23
+ end
24
+
25
+ def [](key)
26
+ instance_variable_get("@#{key}")
27
+ end
28
+
29
+ def []=(key, value)
30
+ @literal_properties[key].check(value)
31
+ instance_variable_set("@#{key}", value)
32
+ end
33
+
34
+ def deconstruct
35
+ to_h.values
36
+ end
37
+
38
+ def deconstruct_keys(keys)
39
+ h = to_h
40
+ keys ? h.slice(*keys) : h
41
+ end
42
+
43
+ def as_pack
44
+ marshal_dump
45
+ end
46
+
47
+ def marshal_load(payload)
48
+ _version, attributes, was_frozen = payload
49
+
50
+ attributes.each do |key, value|
51
+ instance_variable_set("@#{key}", value)
52
+ end
53
+
54
+ freeze if was_frozen
55
+ end
56
+
57
+ def marshal_dump
58
+ [1, to_h, frozen?]
59
+ end
60
+ end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Literal::Enum
4
+ extend Literal::Properties
5
+
6
+ class << self
7
+ include Enumerable
8
+
9
+ attr_reader :members
10
+
11
+ def values = @values.keys
12
+
13
+ def prop(name, type, kind = :keyword, reader: :public, default: nil)
14
+ super(name, type, kind, reader:, writer: false, default:)
15
+ end
16
+
17
+ def inherited(subclass)
18
+ subclass.instance_exec do
19
+ @values = {}
20
+ @members = Set[]
21
+ @indexes = {}
22
+ @index = {}
23
+ end
24
+ end
25
+
26
+ def index(name, type, unique: true, &block)
27
+ @indexes[name] = [type, unique, block || name.to_proc]
28
+ end
29
+
30
+ def where(**kwargs)
31
+ unless kwargs.length == 1
32
+ raise ArgumentError.new("You can only specify one index when using `where`.")
33
+ end
34
+
35
+ key, value = kwargs.first
36
+
37
+ unless (type = @indexes.fetch(key)[0]) === value
38
+ raise Literal::TypeError.expected(value, to_be_a: type)
39
+ end
40
+
41
+ @index.fetch(key)[value]
42
+ end
43
+
44
+ def find_by(**kwargs)
45
+ unless kwargs.length == 1
46
+ raise ArgumentError.new("You can only specify one index when using `where`.")
47
+ end
48
+
49
+ key, value = kwargs.first
50
+
51
+ unless @indexes.fetch(key)[1]
52
+ raise ArgumentError.new("You can only use `find_by` on unique indexes.")
53
+ end
54
+
55
+ unless (type = @indexes.fetch(key)[0]) === value
56
+ raise Literal::TypeError.expected(value, to_be_a: type)
57
+ end
58
+
59
+ @index.fetch(key)[value]&.first
60
+ end
61
+
62
+ def _load(data)
63
+ self[Marshal.load(data)]
64
+ end
65
+
66
+ def const_added(name)
67
+ raise ArgumentError if frozen?
68
+ object = const_get(name)
69
+
70
+ if self === object
71
+ object.instance_variable_set(:@name, name)
72
+ @values[object.value] = object
73
+ @members << object
74
+ define_method("#{name.to_s.gsub(/([^A-Z])([A-Z]+)/, '\1_\2').downcase}?") { self == object }
75
+ object.freeze
76
+ end
77
+ end
78
+
79
+ def new(*, **, &block)
80
+ raise ArgumentError if frozen?
81
+ new_object = super(*, **, &nil)
82
+
83
+ if block
84
+ new_object.instance_exec(&block)
85
+ end
86
+
87
+ new_object
88
+ end
89
+
90
+ def __after_defined__
91
+ raise ArgumentError if frozen?
92
+
93
+ @indexes.each do |name, (type, unique, block)|
94
+ index = @members.group_by(&block).freeze
95
+
96
+ index.each do |key, values|
97
+ unless type === key
98
+ raise Literal::TypeError.expected(key, to_be_a: type)
99
+ end
100
+
101
+ if unique && values.size > 1
102
+ raise ArgumentError.new("The index #{name} is not unique.")
103
+ end
104
+ end
105
+
106
+ @index[name] = index
107
+ end
108
+
109
+ @values.freeze
110
+ @members.freeze
111
+ freeze
112
+ end
113
+
114
+ def each(&)
115
+ @members.each(&)
116
+ end
117
+
118
+ def each_value(&)
119
+ @values.each_key(&)
120
+ end
121
+
122
+ def [](value)
123
+ @values[value]
124
+ end
125
+
126
+ alias_method :cast, :[]
127
+
128
+ def fetch(...)
129
+ @values.fetch(...)
130
+ end
131
+
132
+ def to_proc
133
+ method(:cast).to_proc
134
+ end
135
+
136
+ def to_h(*args)
137
+ @values.dup
138
+ end
139
+ end
140
+
141
+ def initialize(name, value, &block)
142
+ @name = name
143
+ @value = value
144
+ instance_exec(&block) if block
145
+ freeze
146
+ end
147
+
148
+ attr_reader :value
149
+
150
+ def name
151
+ "#{self.class.name}::#{@name}"
152
+ end
153
+
154
+ alias_method :inspect, :name
155
+
156
+ def deconstruct
157
+ [@value]
158
+ end
159
+
160
+ def deconstruct_keys(keys)
161
+ h = to_h
162
+ keys ? h.slice(*keys) : h
163
+ end
164
+
165
+ def _dump(level)
166
+ Marshal.dump(@value)
167
+ end
168
+ end
169
+
170
+ TracePoint.trace(:end) do |tp|
171
+ it = tp.self
172
+
173
+ if Class === it && it < Literal::Enum
174
+ it.__after_defined__
175
+ end
176
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Literal::ArgumentError < ArgumentError
4
+ include Literal::Error
5
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Literal::Error
4
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Literal::TypeError < TypeError
4
+ include Literal::Error
5
+
6
+ def self.expected(value, to_be_a:)
7
+ type = to_be_a
8
+ new("Expected `#{value.inspect}` to be of type: `#{type.inspect}`.")
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Literal::Null
4
+ def self.inspect
5
+ "Literal::Null"
6
+ end
7
+
8
+ freeze
9
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Literal::Object
4
+ extend Literal::Properties
5
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Literal::Properties::DataSchema < Literal::Properties::Schema
4
+ def generate_initializer_body(buffer = +"")
5
+ buffer << "properties = self.class.literal_properties.properties_index\n"
6
+ generate_initializer_handle_properties(@sorted_properties, buffer)
7
+ buffer << "after_initialize if respond_to?(:after_initialize)\nfreeze\n"
8
+ end
9
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ class Literal::Properties::Schema
5
+ include Enumerable
6
+
7
+ def initialize(properties_index: {}, sorted_properties: [])
8
+ @properties_index = properties_index
9
+ @sorted_properties = sorted_properties
10
+ @mutex = Mutex.new
11
+ end
12
+
13
+ attr_reader :properties_index
14
+
15
+ def [](key)
16
+ @properties_index[key]
17
+ end
18
+
19
+ def <<(value)
20
+ @mutex.synchronize do
21
+ @properties_index[value.name] = value
22
+ @sorted_properties = @properties_index.values.sort!
23
+ end
24
+
25
+ self
26
+ end
27
+
28
+ def dup
29
+ self.class.new(
30
+ properties_index: @properties_index.dup,
31
+ sorted_properties: @sorted_properties.dup,
32
+ )
33
+ end
34
+
35
+ def each(&)
36
+ @sorted_properties.each(&)
37
+ end
38
+
39
+ def size
40
+ @sorted_properties.size
41
+ end
42
+
43
+ def generate_initializer(buffer = +"")
44
+ buffer << "def initialize(#{generate_initializer_params})\n"
45
+ generate_initializer_body(buffer)
46
+ buffer << "end\n"
47
+ end
48
+
49
+ def generate_to_h(buffer = +"")
50
+ buffer << "def to_h\n" << "{\n"
51
+
52
+ sorted_properties = @sorted_properties
53
+ i, n = 0, sorted_properties.size
54
+ while i < n
55
+ property = sorted_properties[i]
56
+ buffer << property.name.name << ": @" << property.name.name << ",\n"
57
+ i += 1
58
+ end
59
+
60
+ buffer << "}\n" << "end\n"
61
+ end
62
+
63
+ private
64
+
65
+ def generate_initializer_params(buffer = +"")
66
+ sorted_properties = @sorted_properties
67
+ i, n = 0, sorted_properties.size
68
+ while i < n
69
+ property = sorted_properties[i]
70
+
71
+ case property.kind
72
+ when :*
73
+ buffer << "*" << property.escaped_name
74
+ when :**
75
+ buffer << "**" << property.escaped_name
76
+ when :&
77
+ buffer << "&" << property.escaped_name
78
+ when :positional
79
+ if property.default?
80
+ buffer << property.escaped_name << " = Literal::Null"
81
+ elsif property.type === nil # optional
82
+ buffer << property.escaped_name << " = nil"
83
+ else # required
84
+ buffer << property.escaped_name
85
+ end
86
+ else # keyword
87
+ if property.default?
88
+ buffer << property.name.name << ": Literal::Null"
89
+ elsif property.type === nil
90
+ buffer << property.name.name << ": nil" # optional
91
+ else # required
92
+ buffer << property.name.name << ":"
93
+ end
94
+ end
95
+
96
+ i += 1
97
+ buffer << ", " if i < n
98
+ end
99
+
100
+ buffer
101
+ end
102
+
103
+ def generate_initializer_body(buffer = +"")
104
+ buffer << "properties = self.class.literal_properties.properties_index\n"
105
+ generate_initializer_handle_properties(@sorted_properties, buffer)
106
+ buffer << "after_initialize if respond_to?(:after_initialize)\n"
107
+ end
108
+
109
+ def generate_initializer_handle_properties(properties, buffer = +"")
110
+ i, n = 0, properties.size
111
+ while i < n
112
+ properties[i].generate_initializer_handle_property(buffer)
113
+ i += 1
114
+ end
115
+
116
+ buffer
117
+ end
118
+ end