literal 0.1.0 → 0.2.0

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