literal 1.0.0.rc1 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75c7cc8853143bd8f4ac74b7fc4a5b5d2812bd39c0b4f696a87c2ffc0fb31046
4
- data.tar.gz: 7781d6362e9ae8f75d0156809f95b0c1f8a37df855356173c9b6f09428419bc0
3
+ metadata.gz: b459adebd387b12382d4e049a1f9211f6330bc6f77d66a8380bfbe758a4fb224
4
+ data.tar.gz: d771162ac9e9939e20b9478225baa23dc5d3b4d2fd0f0daa3c821277053ea88f
5
5
  SHA512:
6
- metadata.gz: 6acc2552c11ff3c5f12877b5597f9a6a138bf4901f5fce3801004e286b522c6263fa2454621ac4f5511f553af073d5113d96cd1af1045bc9e44c014c15ab7420
7
- data.tar.gz: 6f9b205b3343426385185ebd0ea0e04164f6a06f9d42d6cb34c542c8d12a98ab327e274b336fb13d389e7380f5bd79831eb2b508f2d019cd6143658db40f3195
6
+ metadata.gz: 68c949539f7002388ccff9c0547c48988a3ccb9ae2cb3262f8ab9119f0187f8ff775c46d3b2aa8abbcc1bbb68a767427c99b48af898744c58bfa004fec3dd491
7
+ data.tar.gz: 872aeda1f2efb4023e9c34c324e8fba149737d5744bc735e57b0ffdbb52be6001f39360fa86c5f981d70e70b5698394eef228eef9dde2d5ca387180414aac555
data/README.md CHANGED
@@ -1,3 +1,3 @@
1
- # A Literal Ruby Gem [WIP]
1
+ # A Literal Ruby Gem
2
2
 
3
3
  See the website for [documentation](https://literal.fun/docs).
data/lib/literal/enum.rb CHANGED
@@ -142,8 +142,17 @@ class Literal::Enum
142
142
  @values.fetch(...)
143
143
  end
144
144
 
145
+ def coerce(value)
146
+ case value
147
+ when self
148
+ value
149
+ else
150
+ self[value]
151
+ end
152
+ end
153
+
145
154
  def to_proc
146
- method(:cast).to_proc
155
+ method(:coerce).to_proc
147
156
  end
148
157
 
149
158
  def to_h(*args)
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Literal::Flags
4
+ include Enumerable
5
+
6
+ def initialize(value = 0, **new_flags)
7
+ if new_flags.length > 0
8
+ value = self.class.calculate_from_hash(new_flags)
9
+ end
10
+
11
+ @value = value
12
+
13
+ freeze
14
+ end
15
+
16
+ def __initialize_from_value__(value)
17
+ @value = value
18
+ freeze
19
+ end
20
+
21
+ attr_reader :value
22
+
23
+ def self.define(**flags)
24
+ raise ArgumentError if frozen?
25
+ unique_values = flags.values
26
+ unique_values.uniq!
27
+
28
+ if unique_values.length != flags.length
29
+ raise Literal::ArgumentError.new("Flags must be unique.")
30
+ end
31
+
32
+ const_set(:FLAGS, flags.dup.freeze)
33
+
34
+ flags.each do |name, bit|
35
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
36
+ # frozen_string_literal: true
37
+
38
+ def #{name}?
39
+ @value & (2 ** #{bit}) > 0
40
+ end
41
+ RUBY
42
+ end
43
+
44
+ freeze
45
+ end
46
+
47
+ # () -> (Integer) -> Literal::Flags
48
+ def self.to_proc
49
+ proc { |value| new(value) }
50
+ end
51
+
52
+ # (Integer) -> String
53
+ def self.calculate_bit_string(value)
54
+ value.to_s(2).rjust(self::BITS, "0")
55
+ end
56
+
57
+ # (Array(Boolean)) -> Integer
58
+ def self.calculate_from_array(array)
59
+ array.reverse_each.with_index.reduce(0) do |value, (bit, index)|
60
+ value | (bit ? 1 << index : 0)
61
+ end
62
+ end
63
+
64
+ # (Hash(Symbol, Boolean)) -> Integer
65
+ def self.calculate_from_hash(hash)
66
+ flags = self::FLAGS
67
+ hash.reduce(0) do |value, (key, bit)|
68
+ value | (bit ? 2 ** flags.fetch(key) : 0)
69
+ end
70
+ end
71
+
72
+ # (Integer) -> Array(Symbol)
73
+ def self.calculate_tokens(value)
74
+ flags = self::FLAGS
75
+ flags.keys.select { |t| value & (2 ** flags.fetch(t)) > 0 }
76
+ end
77
+
78
+ # (Integer) -> Array(Boolean)
79
+ def self.calculate_array(value)
80
+ bits = self::BITS
81
+ Array.new(bits) { |i| (value & (2 ** (bits - 1 - i)) > 0) }
82
+ end
83
+
84
+ # (Array(Symbol)) -> Integer
85
+ def self.calculate_from_tokens(tokens)
86
+ flags = self::FLAGS
87
+ tokens.reduce(0) { |f, t| f | (2 ** flags.fetch(t)) }
88
+ end
89
+
90
+ # (Integer) -> Hash(Symbol, Boolean)
91
+ def self.calculate_hash_from_value(value)
92
+ self::FLAGS.transform_values do |bit|
93
+ (value & (2 ** bit)) > 0
94
+ end
95
+ end
96
+
97
+ # () -> Array(Symbol)
98
+ def self.keys
99
+ @flags.keys
100
+ end
101
+
102
+ # (String) -> Literal::Flags
103
+ def self.from_bit_string(bit_string)
104
+ from_int(bit_string.to_i(2))
105
+ end
106
+
107
+ # (Array(Boolean)) -> Literal::Flags
108
+ def self.from_array(array)
109
+ if array.length != self::BITS
110
+ raise Literal::ArgumentError.new("The array must have #{self::BITS} items.")
111
+ end
112
+
113
+ from_int(calculate_from_array(array))
114
+ end
115
+
116
+ # (Array(Symbol)) -> Literal::Flags
117
+ def self.from_tokens(tokens)
118
+ from_int(calculate_from_tokens(tokens))
119
+ end
120
+
121
+ # (Integer) -> Literal::Flags
122
+ def self.from_int(value)
123
+ allocate.__initialize_from_value__(value)
124
+ end
125
+
126
+ # (String) -> Integer
127
+ def self.unpack_int(value)
128
+ value.unpack1(self::PACKER)
129
+ end
130
+
131
+ # (String) -> Literal::Flags
132
+ def self.unpack(value)
133
+ new(unpack_int(value))
134
+ end
135
+
136
+ # () -> String
137
+ def pack
138
+ [@value].pack(self.class::PACKER)
139
+ end
140
+
141
+ # () -> String
142
+ def inspect
143
+ to_h.inspect
144
+ end
145
+
146
+ # () -> Hash(Symbol, Boolean)
147
+ def to_h
148
+ self.class.calculate_hash_from_value(@value)
149
+ end
150
+
151
+ def each
152
+ self.class::FLAGS.each do |key, bit|
153
+ yield key, @value & (2 ** bit) > 0
154
+ end
155
+ end
156
+
157
+ # (Symbol) -> Boolean
158
+ def [](key)
159
+ @value & (2 ** self.class::FLAGS.fetch(key)) > 0
160
+ end
161
+
162
+ def |(other)
163
+ case other
164
+ when Literal::Flags
165
+ self.class.new(@value | other.value)
166
+ when Integer
167
+ self.class.new(@value | other)
168
+ end
169
+ end
170
+
171
+ def &(other)
172
+ case other
173
+ when Literal::Flags
174
+ self.class.new(@value & other.value)
175
+ when Integer
176
+ self.class.new(@value & other)
177
+ end
178
+ end
179
+
180
+ def to_i
181
+ @value
182
+ end
183
+
184
+ def to_bit_string
185
+ self.class.calculate_bit_string(@value)
186
+ end
187
+
188
+ def to_tokens
189
+ self.class.calculate_tokens(@value)
190
+ end
191
+
192
+ def to_a
193
+ self.class.calculate_array(@value)
194
+ end
195
+
196
+ alias_method :deconstruct, :to_a
197
+
198
+ def deconstruct_keys(keys = nil)
199
+ if keys
200
+ flags = self.class::FLAGS
201
+ keys.to_h do |key|
202
+ [key, @value & (2 ** flags.fetch(key)) > 0]
203
+ end
204
+ else
205
+ to_h
206
+ end
207
+ end
208
+ end
209
+
210
+ class Literal::Flags8 < Literal::Flags
211
+ BYTES = 1
212
+ BITS = BYTES * 8
213
+ PACKER = "C"
214
+ end
215
+
216
+ class Literal::Flags16 < Literal::Flags
217
+ BYTES = 2
218
+ BITS = BYTES * 8
219
+ PACKER = "S"
220
+ end
221
+
222
+ class Literal::Flags32 < Literal::Flags
223
+ BYTES = 4
224
+ BITS = BYTES * 8
225
+ PACKER = "L"
226
+ end
227
+
228
+ class Literal::Flags64 < Literal::Flags
229
+ BYTES = 8
230
+ BITS = BYTES * 8
231
+ PACKER = "Q"
232
+ end
@@ -47,8 +47,10 @@ class Literal::Properties::Schema
47
47
  end
48
48
 
49
49
  def generate_initializer(buffer = +"")
50
- buffer << "alias initialize initialize\n"
51
- buffer << "def initialize(#{generate_initializer_params})\n"
50
+ buffer << "alias initialize initialize\n" \
51
+ "def initialize("
52
+ generate_initializer_params(buffer)
53
+ buffer << ")\n"
52
54
  generate_initializer_body(buffer)
53
55
  buffer << "" \
54
56
  "rescue Literal::TypeError => error\n" \
@@ -100,7 +102,7 @@ class Literal::Properties::Schema
100
102
  i, n = 0, sorted_properties.size
101
103
  while i < n
102
104
  property = sorted_properties[i]
103
- buffer << " @" << property.name.name << " == other.#{property.escaped_name}"
105
+ buffer << " @" << property.name.name << " == other." << property.escaped_name
104
106
  buffer << " &&\n " if i < n - 1
105
107
  i += 1
106
108
  end
@@ -25,6 +25,21 @@ class Literal::Types::ConstraintType
25
25
  true
26
26
  end
27
27
 
28
+ def record_literal_type_errors(context)
29
+ @object_constraints.each do |constraint|
30
+ next if constraint === context.actual
31
+
32
+ context.add_child(label: inspect, expected: constraint, actual: context.actual)
33
+ end
34
+
35
+ @property_constraints.each do |property, constraint|
36
+ actual = context.actual.public_send(property)
37
+ next if constraint === actual
38
+
39
+ context.add_child(label: ".#{property}", expected: constraint, actual:)
40
+ end
41
+ end
42
+
28
43
  private
29
44
 
30
45
  def inspect_constraints
@@ -18,4 +18,21 @@ class Literal::Types::HashType
18
18
 
19
19
  true
20
20
  end
21
+
22
+ def record_literal_type_errors(context)
23
+ unless Hash === context.actual
24
+ return
25
+ end
26
+
27
+ context.actual.each do |key, item|
28
+ unless @key_type === key
29
+ context.add_child(label: "[]", expected: @key_type, actual: key)
30
+ next
31
+ end
32
+
33
+ unless @value_type === item
34
+ context.add_child(label: "[#{key.inspect}]", expected: @value_type, actual: item)
35
+ end
36
+ end
37
+ end
21
38
  end
@@ -17,4 +17,12 @@ class Literal::Types::IntersectionType
17
17
  def nil?
18
18
  @types.all?(&:nil?)
19
19
  end
20
+
21
+ def record_literal_type_errors(context)
22
+ @types.each do |type|
23
+ next if type === context.actual
24
+
25
+ context.add_child(label: inspect, expected: type, actual: context.actual)
26
+ end
27
+ end
20
28
  end
@@ -22,8 +22,13 @@ class Literal::Types::MapType
22
22
  end
23
23
 
24
24
  context.actual.each do |key, item|
25
- unless @shape[key] === item
26
- context.add_child(label: "[#{key.inspect}]", expected: @shape[key], actual: item)
25
+ unless (expected = @shape[key])
26
+ context.add_child(label: "[]", expected: @shape.keys, actual: key)
27
+ next
28
+ end
29
+
30
+ unless expected === item
31
+ context.add_child(label: "[#{key.inspect}]", expected:, actual: item)
27
32
  end
28
33
  end
29
34
  end
@@ -23,4 +23,20 @@ class Literal::Types::TupleType
23
23
 
24
24
  true
25
25
  end
26
+
27
+ def record_literal_type_errors(context)
28
+ return unless Array === context.actual
29
+
30
+ len = [@types.size, context.actual.size].max
31
+ i = 0
32
+ while i < len
33
+ actual = context.actual[i]
34
+ if !(expected = @types[i])
35
+ context.add_child(label: "[#{i}]", expected: Literal::Types::NeverType, actual:)
36
+ elsif !(expected === actual)
37
+ context.add_child(label: "[#{i}]", expected:, actual:)
38
+ end
39
+ i += 1
40
+ end
41
+ end
26
42
  end
data/lib/literal/types.rb CHANGED
@@ -25,7 +25,6 @@ module Literal::Types
25
25
  autoload :ProcableType, "literal/types/procable_type"
26
26
  autoload :RangeType, "literal/types/range_type"
27
27
  autoload :SetType, "literal/types/set_type"
28
- autoload :ShapeType, "literal/types/shape_type"
29
28
  autoload :StringType, "literal/types/string_type"
30
29
  autoload :SymbolType, "literal/types/symbol_type"
31
30
  autoload :TruthyType, "literal/types/truthy_type"
@@ -39,7 +38,7 @@ module Literal::Types
39
38
  NilableLambdaType = NilableType.new(LambdaType)
40
39
  NilableProcableType = NilableType.new(ProcableType)
41
40
 
42
- # Matches any value except `nil`. Use `_Any?` or `_Unit` to match any value including `nil`.
41
+ # Matches any value except `nil`. Use `_Any?` or `_Void` to match any value including `nil`.
43
42
  def _Any
44
43
  AnyType
45
44
  end
@@ -293,20 +292,6 @@ module Literal::Types
293
292
  )
294
293
  end
295
294
 
296
- # Ensures a value matches the given shape of a Hash
297
- def _Shape(...)
298
- ShapeType.new(...)
299
- end
300
-
301
- # Nilable version of `_Shape`
302
- def _Shape?(...)
303
- NilableType.new(
304
- ShapeType.new(...),
305
- )
306
- end
307
-
308
- # Nilable version of `_Shape`
309
-
310
295
  # Matches if the value is a `String` and matches the given constraints.
311
296
  # If you don't need any constraints, use `String` instead of `_String`.
312
297
  def _String(...)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Literal
4
- VERSION = "1.0.0.rc1"
4
+ VERSION = "1.1.0"
5
5
  end
data/lib/literal.rb CHANGED
@@ -13,6 +13,11 @@ module Literal
13
13
  autoload :Property, "literal/property"
14
14
  autoload :Struct, "literal/struct"
15
15
  autoload :Types, "literal/types"
16
+ autoload :Flags, "literal/flags"
17
+ autoload :Flags8, "literal/flags"
18
+ autoload :Flags16, "literal/flags"
19
+ autoload :Flags32, "literal/flags"
20
+ autoload :Flags64, "literal/flags"
16
21
 
17
22
  # Errors
18
23
  autoload :Error, "literal/errors/error"
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: literal
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.rc1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Drapper
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-26 00:00:00.000000000 Z
11
+ date: 2024-11-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Enums, properties, generics, structured objects.
13
+ description: A literal Ruby gem.
14
14
  email:
15
15
  - joel@drapper.me
16
16
  executables: []
@@ -27,6 +27,7 @@ files:
27
27
  - lib/literal/errors/argument_error.rb
28
28
  - lib/literal/errors/error.rb
29
29
  - lib/literal/errors/type_error.rb
30
+ - lib/literal/flags.rb
30
31
  - lib/literal/null.rb
31
32
  - lib/literal/object.rb
32
33
  - lib/literal/properties.rb
@@ -71,11 +72,11 @@ files:
71
72
  - lib/literal/types/union_type.rb
72
73
  - lib/literal/types/void_type.rb
73
74
  - lib/literal/version.rb
74
- homepage: https://github.com/joeldrapper/literal
75
+ homepage: https://literal.fun
75
76
  licenses:
76
77
  - MIT
77
78
  metadata:
78
- homepage_uri: https://github.com/joeldrapper/literal
79
+ homepage_uri: https://literal.fun
79
80
  source_code_uri: https://github.com/joeldrapper/literal
80
81
  changelog_uri: https://github.com/joeldrapper/literal/blob/main/CHANGELOG.md
81
82
  funding_uri: https://github.com/sponsors/joeldrapper
@@ -98,5 +99,5 @@ requirements: []
98
99
  rubygems_version: 3.5.18
99
100
  signing_key:
100
101
  specification_version: 4
101
- summary: A literal Ruby gem.
102
+ summary: Enums, properties, generics, structured objects and runtime type checking.
102
103
  test_files: []