dhall 0.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.
@@ -0,0 +1,277 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cbor"
4
+ require "digest/sha2"
5
+
6
+ require "dhall/ast"
7
+ require "dhall/builtins"
8
+
9
+ module Dhall
10
+ def self.from_binary(cbor_binary)
11
+ decode(CBOR.decode(cbor_binary))
12
+ end
13
+
14
+ def self.decode(expression)
15
+ BINARY.each do |match, use|
16
+ return use[expression] if expression.is_a?(match)
17
+ end
18
+
19
+ raise "Unknown expression: #{expression.inspect}"
20
+ end
21
+
22
+ class Expression
23
+ def self.decode(*args)
24
+ return new(value: args.first) if args.length == 1
25
+
26
+ new(*args)
27
+ end
28
+
29
+ def to_cbor(packer=nil)
30
+ if packer
31
+ packer.write(as_json)
32
+ packer
33
+ else
34
+ CBOR.encode(as_json)
35
+ end
36
+ end
37
+ alias to_binary to_cbor
38
+
39
+ def digest(digest: Digest::SHA2.new(256))
40
+ (digest << normalize.to_binary).freeze
41
+ end
42
+
43
+ def cache_key
44
+ "sha256:#{digest.hexdigest}"
45
+ end
46
+ end
47
+
48
+ class Application
49
+ def self.decode(function, *args)
50
+ function = Dhall.decode(function)
51
+ args.map(&Dhall.method(:decode)).reduce(function) do |f, arg|
52
+ self.for(function: f, argument: arg)
53
+ end
54
+ end
55
+ end
56
+
57
+ class Function
58
+ def self.decode(var_or_type, type_or_body, body_or_nil=nil)
59
+ type_or_body = Dhall.decode(type_or_body)
60
+
61
+ if body_or_nil.nil?
62
+ of_arguments(Dhall.decode(var_or_type), body: type_or_body)
63
+ else
64
+ raise ArgumentError, "explicit var named _" if var_or_type == "_"
65
+
66
+ body_or_nil = Dhall.decode(body_or_nil)
67
+ new(var: var_or_type, type: type_or_body, body: body_or_nil)
68
+ end
69
+ end
70
+ end
71
+
72
+ class Operator
73
+ def self.decode(opcode, lhs, rhs)
74
+ OPERATORS[opcode].new(
75
+ lhs: Dhall.decode(lhs),
76
+ rhs: Dhall.decode(rhs)
77
+ )
78
+ end
79
+ end
80
+
81
+ class List
82
+ def self.decode(type, *els)
83
+ type = type.nil? ? nil : Dhall.decode(type)
84
+ if els.empty?
85
+ EmptyList.new(element_type: type)
86
+ else
87
+ List.new(elements: els.map(&Dhall.method(:decode)), element_type: type)
88
+ end
89
+ end
90
+ end
91
+
92
+ class Optional
93
+ def self.decode(type, value=nil)
94
+ if value.nil?
95
+ OptionalNone.new(value_type: Dhall.decode(type))
96
+ else
97
+ Optional.new(
98
+ value: Dhall.decode(value),
99
+ value_type: type.nil? ? type : Dhall.decode(type)
100
+ )
101
+ end
102
+ end
103
+ end
104
+
105
+ class Merge
106
+ def self.decode(record, input, type=nil)
107
+ new(
108
+ record: Dhall.decode(record),
109
+ input: Dhall.decode(input),
110
+ type: type.nil? ? nil : Dhall.decode(type)
111
+ )
112
+ end
113
+ end
114
+
115
+ class RecordType
116
+ def self.decode(record)
117
+ if record.empty?
118
+ EmptyRecordType.new
119
+ else
120
+ new(record: Hash[record.map { |k, v| [k, Dhall.decode(v)] }])
121
+ end
122
+ end
123
+ end
124
+
125
+ class Record
126
+ def self.decode(record)
127
+ if record.empty?
128
+ EmptyRecord.new
129
+ else
130
+ new(record: Hash[record.map { |k, v| [k, Dhall.decode(v)] }])
131
+ end
132
+ end
133
+ end
134
+
135
+ class RecordSelection
136
+ def self.decode(record, selector)
137
+ new(record: Dhall.decode(record), selector: selector)
138
+ end
139
+ end
140
+
141
+ class RecordProjection
142
+ def self.decode(record, *selectors)
143
+ if selectors.empty?
144
+ EmptyRecordProjection.new(record: Dhall.decode(record))
145
+ else
146
+ new(record: Dhall.decode(record), selectors: selectors)
147
+ end
148
+ end
149
+ end
150
+
151
+ class UnionType
152
+ def self.decode(record)
153
+ new(alternatives: Hash[record.map do |k, v|
154
+ [k, v.nil? ? v : Dhall.decode(v)]
155
+ end])
156
+ end
157
+ end
158
+
159
+ class Union
160
+ def self.decode(tag, value, alternatives)
161
+ new(
162
+ tag: tag,
163
+ value: Dhall.decode(value),
164
+ alternatives: UnionType.decode(alternatives)
165
+ )
166
+ end
167
+ end
168
+
169
+ class If
170
+ def self.decode(pred, thn, els)
171
+ new(
172
+ predicate: Dhall.decode(pred),
173
+ then: Dhall.decode(thn),
174
+ else: Dhall.decode(els)
175
+ )
176
+ end
177
+ end
178
+
179
+ class TextLiteral
180
+ def self.decode(*chunks)
181
+ lead_text, *pairs = chunks
182
+ chunks =
183
+ [Text.new(value: lead_text)] +
184
+ pairs.each_slice(2).flat_map do |(e, t)|
185
+ [Dhall.decode(e), Text.new(value: t)]
186
+ end
187
+
188
+ chunks.length == 1 ? chunks.first : TextLiteral.new(chunks: chunks)
189
+ end
190
+ end
191
+
192
+ class Import
193
+ def self.decode(integrity_check, import_type, path_type, *parts)
194
+ parts[0] = Dhall.decode(parts[0]) if path_type < 2 && !parts[0].nil?
195
+ parts[0] = EnvironmentVariable.decode(parts[0]) if path_type == 6
196
+
197
+ new(
198
+ IntegrityCheck.new(*integrity_check),
199
+ IMPORT_TYPES[import_type],
200
+ PATH_TYPES[path_type].new(*parts)
201
+ )
202
+ end
203
+ end
204
+
205
+ class LetBlock
206
+ def self.decode(*parts)
207
+ body = Dhall.decode(parts.pop)
208
+ lets = parts.each_slice(3).map do |(var, type, assign)|
209
+ Let.new(
210
+ var: var,
211
+ assign: Dhall.decode(assign),
212
+ type: type.nil? ? nil : Dhall.decode(type)
213
+ )
214
+ end
215
+
216
+ self.for(lets: lets, body: body)
217
+ end
218
+ end
219
+
220
+ class TypeAnnotation
221
+ def self.decode(value, type)
222
+ new(value: Dhall.decode(value), type: Dhall.decode(type))
223
+ end
224
+ end
225
+
226
+ BINARY = {
227
+ ::TrueClass => ->(e) { Bool.new(value: e) },
228
+ ::FalseClass => ->(e) { Bool.new(value: e) },
229
+ ::Float => ->(e) { Double.new(value: e) },
230
+ ::String => ->(e) { Builtins::ALL[e]&.new || Variable.new(name: e) },
231
+ ::Integer => ->(e) { Variable.new(index: e) },
232
+ ::Array => lambda { |e|
233
+ if e.length == 2 && e.first.is_a?(::String)
234
+ Variable.new(name: e[0], index: e[1])
235
+ else
236
+ tag, *body = e
237
+ BINARY_TAGS[tag]&.decode(*body) ||
238
+ (raise "Unknown expression: #{e.inspect}")
239
+ end
240
+ },
241
+ ::CBOR::Tagged => lambda { |e|
242
+ return Dhall.decode(e.value) if e.tag == 55799
243
+
244
+ raise "Unknown tag: #{e.inspect}"
245
+ }
246
+ }.freeze
247
+
248
+ BINARY_TAGS = [
249
+ Application,
250
+ Function,
251
+ Forall,
252
+ Operator,
253
+ List,
254
+ Optional,
255
+ Merge,
256
+ RecordType,
257
+ Record,
258
+ RecordSelection,
259
+ RecordProjection,
260
+ UnionType,
261
+ Union,
262
+ nil,
263
+ If,
264
+ Natural,
265
+ Integer,
266
+ nil,
267
+ TextLiteral,
268
+ nil,
269
+ nil,
270
+ nil,
271
+ nil,
272
+ nil,
273
+ Import,
274
+ LetBlock,
275
+ TypeAnnotation
276
+ ].freeze
277
+ end
@@ -0,0 +1,425 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dhall/ast"
4
+
5
+ module Dhall
6
+ class Builtin < Expression
7
+ include(ValueSemantics.for_attributes {})
8
+
9
+ def call(*args)
10
+ # Do not auto-normalize builtins to avoid recursion loop
11
+ args.reduce(self) do |f, arg|
12
+ Application.new(function: f, argument: arg)
13
+ end
14
+ end
15
+
16
+ def unfill
17
+ attributes.reduce(self.class.new) do |f, attr|
18
+ if send(attr.name).nil?
19
+ f
20
+ else
21
+ Application.new(function: f, argument: send(attr.name))
22
+ end
23
+ end
24
+ end
25
+
26
+ def as_json
27
+ if (unfilled = unfill).class != self.class
28
+ unfilled.as_json
29
+ else
30
+ self.class.name&.split(/::/)&.last&.tr("_", "/").to_s
31
+ end
32
+ end
33
+
34
+ protected
35
+
36
+ def attributes
37
+ self.class.value_semantics.attributes
38
+ end
39
+
40
+ def fill_next_if_valid(value)
41
+ with(attributes.each_with_object({}) do |attr, h|
42
+ if !send(attr.name).nil?
43
+ h[attr.name] = send(attr.name)
44
+ elsif attr.validate?(value)
45
+ h[attr.name] = value
46
+ value = nil
47
+ else
48
+ return nil
49
+ end
50
+ end)
51
+ end
52
+
53
+ def full?
54
+ attributes.all? { |attr| !send(attr.name).nil? }
55
+ end
56
+
57
+ def fill_or_call(arg, &block)
58
+ full? ? block[arg] : fill_next_if_valid(arg)
59
+ end
60
+ end
61
+
62
+ module Builtins
63
+ # rubocop:disable Style/ClassAndModuleCamelCase
64
+
65
+ class Double_show < Builtin
66
+ def call(arg)
67
+ if arg.is_a?(Double)
68
+ Text.new(value: arg.to_s)
69
+ else
70
+ super
71
+ end
72
+ end
73
+ end
74
+
75
+ class Integer_show < Builtin
76
+ def call(arg)
77
+ if arg.is_a?(Integer)
78
+ Text.new(value: arg.to_s)
79
+ else
80
+ super
81
+ end
82
+ end
83
+ end
84
+
85
+ class Integer_toDouble < Builtin
86
+ def call(arg)
87
+ if arg.is_a?(Integer)
88
+ Double.new(value: arg.value.to_f)
89
+ else
90
+ super
91
+ end
92
+ end
93
+ end
94
+
95
+ class Natural_build < Builtin
96
+ def fusion(arg, *bogus)
97
+ if bogus.empty? &&
98
+ arg.is_a?(Application) &&
99
+ arg.function == Natural_fold.new
100
+ arg.argument
101
+ else
102
+ super
103
+ end
104
+ end
105
+
106
+ def call(arg)
107
+ arg.call(
108
+ Variable.new(name: "Natural"),
109
+ Function.of_arguments(
110
+ Variable.new(name: "Natural"),
111
+ body: Variable["_"] + Natural.new(value: 1)
112
+ ),
113
+ Natural.new(value: 0)
114
+ )
115
+ end
116
+ end
117
+
118
+ class Natural_even < Builtin
119
+ def call(nat)
120
+ if nat.is_a?(Natural)
121
+ Bool.new(value: nat.even?)
122
+ else
123
+ super
124
+ end
125
+ end
126
+ end
127
+
128
+ class Natural_fold < Builtin
129
+ include(ValueSemantics.for_attributes do
130
+ nat Either(nil, Natural), default: nil
131
+ type Either(nil, Expression), default: nil
132
+ f Either(nil, Expression), default: nil
133
+ end)
134
+
135
+ def call(arg)
136
+ fill_or_call(arg) do
137
+ if @nat.zero?
138
+ arg.normalize
139
+ else
140
+ @f.call(with(nat: nat.pred).call(arg))
141
+ end
142
+ end || super
143
+ end
144
+ end
145
+
146
+ class Natural_isZero < Builtin
147
+ def call(nat)
148
+ if nat.is_a?(Natural)
149
+ Bool.new(value: nat.zero?)
150
+ else
151
+ super
152
+ end
153
+ end
154
+ end
155
+
156
+ class Natural_odd < Builtin
157
+ def call(nat)
158
+ if nat.is_a?(Natural)
159
+ Bool.new(value: nat.odd?)
160
+ else
161
+ super
162
+ end
163
+ end
164
+ end
165
+
166
+ class Natural_show < Builtin
167
+ def call(nat)
168
+ if nat.is_a?(Natural)
169
+ Text.new(value: nat.to_s)
170
+ else
171
+ super
172
+ end
173
+ end
174
+ end
175
+
176
+ class Natural_toInteger < Builtin
177
+ def call(nat)
178
+ if nat.is_a?(Natural)
179
+ Integer.new(value: nat.value)
180
+ else
181
+ super
182
+ end
183
+ end
184
+ end
185
+
186
+ class List_build < Builtin
187
+ include(ValueSemantics.for_attributes do
188
+ type Either(nil, Expression), default: nil
189
+ end)
190
+
191
+ def fusion(*args)
192
+ _, arg, = args
193
+ if arg.is_a?(Application) &&
194
+ arg.function.is_a?(Application) &&
195
+ arg.function.function == List_fold.new
196
+ arg.argument
197
+ else
198
+ super
199
+ end
200
+ end
201
+
202
+ def call(arg)
203
+ fill_or_call(arg) do
204
+ arg.call(
205
+ Variable["List"].call(type),
206
+ cons,
207
+ EmptyList.new(element_type: type)
208
+ )
209
+ end
210
+ end
211
+
212
+ protected
213
+
214
+ def cons
215
+ Function.of_arguments(
216
+ type,
217
+ Variable["List"].call(type.shift(1, "_", 0)),
218
+ body: List.of(Variable["_", 1]).concat(Variable["_"])
219
+ )
220
+ end
221
+ end
222
+
223
+ class List_fold < Builtin
224
+ include(ValueSemantics.for_attributes do
225
+ ltype Either(nil, Expression), default: nil
226
+ list Either(nil, List), default: nil
227
+ ztype Either(nil, Expression), default: nil
228
+ f Either(nil, Expression), default: nil
229
+ end)
230
+
231
+ def call(arg)
232
+ fill_or_call(arg) do
233
+ list.reduce(arg, &f).normalize
234
+ end || super
235
+ end
236
+ end
237
+
238
+ class List_head < Builtin
239
+ include(ValueSemantics.for_attributes do
240
+ type Either(nil, Expression), default: nil
241
+ end)
242
+
243
+ def call(arg)
244
+ fill_or_call(arg) do
245
+ if arg.is_a?(List)
246
+ arg.first
247
+ else
248
+ super
249
+ end
250
+ end
251
+ end
252
+ end
253
+
254
+ class List_indexed < Builtin
255
+ include(ValueSemantics.for_attributes do
256
+ type Either(nil, Expression), default: nil
257
+ end)
258
+
259
+ def call(arg)
260
+ fill_or_call(arg) do
261
+ if arg.is_a?(List)
262
+ _call(arg)
263
+ else
264
+ super
265
+ end
266
+ end
267
+ end
268
+
269
+ protected
270
+
271
+ def _call(arg)
272
+ arg.map(type: indexed_type(type)) { |x, idx|
273
+ Record.new(
274
+ record: {
275
+ "index" => Natural.new(value: idx),
276
+ "value" => x
277
+ }
278
+ )
279
+ }.normalize
280
+ end
281
+
282
+ def indexed_type(value_type)
283
+ RecordType.new(
284
+ record: {
285
+ "index" => Variable.new(name: "Natural"),
286
+ "value" => value_type
287
+ }
288
+ )
289
+ end
290
+ end
291
+
292
+ class List_last < Builtin
293
+ include(ValueSemantics.for_attributes do
294
+ type Either(nil, Expression), default: nil
295
+ end)
296
+
297
+ def call(arg)
298
+ fill_or_call(arg) do
299
+ if arg.is_a?(List)
300
+ arg.last
301
+ else
302
+ super
303
+ end
304
+ end
305
+ end
306
+ end
307
+
308
+ class List_length < Builtin
309
+ include(ValueSemantics.for_attributes do
310
+ type Either(nil, Expression), default: nil
311
+ end)
312
+
313
+ def call(arg)
314
+ fill_or_call(arg) do
315
+ if arg.is_a?(List)
316
+ Natural.new(value: arg.length)
317
+ else
318
+ super
319
+ end
320
+ end
321
+ end
322
+ end
323
+
324
+ class List_reverse < Builtin
325
+ include(ValueSemantics.for_attributes do
326
+ type Either(nil, Expression), default: nil
327
+ end)
328
+
329
+ def call(arg)
330
+ fill_or_call(arg) do
331
+ if arg.is_a?(List)
332
+ arg.reverse
333
+ else
334
+ super
335
+ end
336
+ end
337
+ end
338
+ end
339
+
340
+ class Optional_build < Builtin
341
+ include(ValueSemantics.for_attributes do
342
+ type Either(nil, Expression), default: nil
343
+ end)
344
+
345
+ def fusion(*args)
346
+ _, arg, = args
347
+ if arg.is_a?(Application) &&
348
+ arg.function.is_a?(Application) &&
349
+ arg.function.function == Optional_fold.new
350
+ arg.argument
351
+ else
352
+ super
353
+ end
354
+ end
355
+
356
+ def call(arg)
357
+ fill_or_call(arg) do
358
+ arg.call(
359
+ Variable["Optional"].call(type),
360
+ some,
361
+ OptionalNone.new(value_type: type)
362
+ )
363
+ end
364
+ end
365
+
366
+ protected
367
+
368
+ def some
369
+ Function.of_arguments(
370
+ type,
371
+ body: Optional.new(
372
+ value: Variable["_"],
373
+ value_type: type
374
+ )
375
+ )
376
+ end
377
+ end
378
+
379
+ class Optional_fold < Builtin
380
+ include(ValueSemantics.for_attributes do
381
+ type Either(nil, Expression), default: nil
382
+ optional Either(nil, Optional), default: nil
383
+ ztype Either(nil, Expression), default: nil
384
+ f Either(nil, Expression), default: nil
385
+ end)
386
+
387
+ def call(*args)
388
+ args.reduce(self) do |fold, arg|
389
+ fold.fill_or_call(arg) do
390
+ fold.optional.reduce(arg, &fold.f)
391
+ end || super
392
+ end
393
+ end
394
+ end
395
+
396
+ class Text_show < Builtin
397
+ ENCODE = (Hash.new { |_, x| "\\u%04x" % x.ord }).merge(
398
+ "\"" => "\\\"",
399
+ "\\" => "\\\\",
400
+ "\b" => "\\b",
401
+ "\f" => "\\f",
402
+ "\n" => "\\n",
403
+ "\r" => "\\r",
404
+ "\t" => "\\t"
405
+ )
406
+
407
+ def call(arg)
408
+ if arg.is_a?(Text)
409
+ Text.new(
410
+ value: "\"#{arg.value.gsub(
411
+ /["\$\\\b\f\n\r\t\u0000-\u001F]/,
412
+ &ENCODE
413
+ )}\""
414
+ )
415
+ else
416
+ super
417
+ end
418
+ end
419
+ end
420
+
421
+ # rubocop:enable Style/ClassAndModuleCamelCase
422
+
423
+ ALL = Hash[constants.map { |c| [c.to_s.tr("_", "/"), const_get(c)] }]
424
+ end
425
+ end