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.
- checksums.yaml +7 -0
- data/COPYING +676 -0
- data/README.md +283 -0
- data/bin/json-to-dhall +8 -0
- data/bin/yaml-to-dhall +8 -0
- data/dhall.gemspec +36 -0
- data/lib/dhall.rb +37 -0
- data/lib/dhall/as_dhall.rb +195 -0
- data/lib/dhall/ast.rb +1559 -0
- data/lib/dhall/binary.rb +277 -0
- data/lib/dhall/builtins.rb +425 -0
- data/lib/dhall/normalize.rb +420 -0
- data/lib/dhall/parser.citrus +500 -0
- data/lib/dhall/parser.rb +601 -0
- data/lib/dhall/resolve.rb +383 -0
- data/lib/dhall/typecheck.rb +1209 -0
- data/lib/dhall/util.rb +130 -0
- data/lib/dhall/visitor.rb +23 -0
- metadata +163 -0
data/lib/dhall/parser.rb
ADDED
@@ -0,0 +1,601 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dhall/ast"
|
4
|
+
require "dhall/builtins"
|
5
|
+
|
6
|
+
module Dhall
|
7
|
+
module Parser
|
8
|
+
def self.parse(*args)
|
9
|
+
CitrusParser.parse(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.parse_file(*args)
|
13
|
+
CitrusParser.parse_file(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
module CompleteExpression
|
17
|
+
def value
|
18
|
+
capture(:expression).value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Expression
|
23
|
+
def value
|
24
|
+
key =
|
25
|
+
[:let_binding, :lambda, :forall, :arrow, :if, :merge]
|
26
|
+
.find { |k| captures.key?(k) }
|
27
|
+
|
28
|
+
return public_send(key) if key
|
29
|
+
|
30
|
+
key =
|
31
|
+
[:empty_collection, :non_empty_optional]
|
32
|
+
.find { |k| captures.key?(k) }
|
33
|
+
key ? capture(key).value : super
|
34
|
+
end
|
35
|
+
|
36
|
+
def let_binding
|
37
|
+
LetBlock.for(
|
38
|
+
lets: captures(:let_binding).map(&:value),
|
39
|
+
body: capture(:expression).value
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def lambda
|
44
|
+
Function.new(
|
45
|
+
var: capture(:nonreserved_label).value,
|
46
|
+
type: captures(:expression)[0].value,
|
47
|
+
body: captures(:expression)[1].value
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def forall
|
52
|
+
Forall.new(
|
53
|
+
var: capture(:nonreserved_label).value,
|
54
|
+
type: captures(:expression)[0].value,
|
55
|
+
body: captures(:expression)[1].value
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def arrow
|
60
|
+
Forall.of_arguments(
|
61
|
+
capture(:operator_expression).value,
|
62
|
+
body: capture(:expression).value
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
def if
|
67
|
+
If.new(
|
68
|
+
predicate: captures(:expression)[0].value,
|
69
|
+
then: captures(:expression)[1].value,
|
70
|
+
else: captures(:expression)[2].value
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
def merge
|
75
|
+
Merge.new(
|
76
|
+
record: captures(:import_expression)[0].value,
|
77
|
+
input: captures(:import_expression)[1].value,
|
78
|
+
type: capture(:application_expression)&.value
|
79
|
+
)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
OPERATORS = {
|
84
|
+
import_alt_expression: :ImportFallback,
|
85
|
+
or_expression: :Or,
|
86
|
+
plus_expression: :Plus,
|
87
|
+
text_append_expression: :TextConcatenate,
|
88
|
+
list_append_expression: :ListConcatenate,
|
89
|
+
and_expression: :And,
|
90
|
+
combine_expression: :RecursiveRecordMerge,
|
91
|
+
prefer_expression: :RightBiasedRecordMerge,
|
92
|
+
combine_types_expression: :RecursiveRecordTypeMerge,
|
93
|
+
times_expression: :Times,
|
94
|
+
equal_expression: :Equal,
|
95
|
+
not_equal_expression: :NotEqual
|
96
|
+
}.freeze
|
97
|
+
|
98
|
+
OPERATORS.to_a.zip(
|
99
|
+
OPERATORS.to_a[1..-1] + [[:application_expression]]
|
100
|
+
).each do |((rule, ast_class), (next_rule, _))|
|
101
|
+
const_set(rule.to_s.split(/_/).map(&:capitalize).join, Module.new do
|
102
|
+
define_method(:value) do
|
103
|
+
captures(next_rule).map(&:value).reduce do |lhs, rhs|
|
104
|
+
Operator.const_get(ast_class).new(lhs: lhs, rhs: rhs)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end)
|
108
|
+
end
|
109
|
+
|
110
|
+
module ApplicationExpression
|
111
|
+
def value
|
112
|
+
some = capture(:some) ? [Variable["Some"]] : []
|
113
|
+
els = some + captures(:import_expression).map(&:value)
|
114
|
+
els.reduce do |f, arg|
|
115
|
+
Application.for(function: f, argument: arg)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
module SelectorExpression
|
121
|
+
def value
|
122
|
+
record = capture(:primitive_expression).value
|
123
|
+
selectors = captures(:selector).map(&:value)
|
124
|
+
selectors.reduce(record) do |rec, sels|
|
125
|
+
if sels.is_a?(Array)
|
126
|
+
return EmptyRecordProjection.new(record: rec) if sels.empty?
|
127
|
+
RecordProjection.new(record: rec, selectors: sels)
|
128
|
+
else
|
129
|
+
RecordSelection.new(record: rec, selector: sels)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
module Labels
|
136
|
+
def value
|
137
|
+
captures(:any_label).map(&:value)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
module Label
|
142
|
+
def value
|
143
|
+
if first.string == "`"
|
144
|
+
matches[1].string
|
145
|
+
else
|
146
|
+
string
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
module NonreservedLabel
|
152
|
+
def value
|
153
|
+
if captures.key?(:label)
|
154
|
+
capture(:label).value
|
155
|
+
else
|
156
|
+
string
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
module NaturalLiteral
|
162
|
+
def value
|
163
|
+
Natural.new(value: string.to_i)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
module IntegerLiteral
|
168
|
+
def value
|
169
|
+
Integer.new(value: string.to_i)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
module NumericDoubleLiteral
|
174
|
+
def value
|
175
|
+
float = string.to_f
|
176
|
+
raise Citrus::ParseError, input if float.nan? || float.infinite?
|
177
|
+
Double.new(value: float)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
module MinusInfinityLiteral
|
182
|
+
def value
|
183
|
+
Double.new(value: -Float::INFINITY)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
module PlusInfinityLiteral
|
188
|
+
def value
|
189
|
+
Double.new(value: Float::INFINITY)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
module Nan
|
194
|
+
def value
|
195
|
+
Double.new(value: Float::NAN)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
module DoubleQuoteLiteral
|
200
|
+
def value
|
201
|
+
TextLiteral.for(
|
202
|
+
*captures(:double_quote_chunk)
|
203
|
+
.map(&:value)
|
204
|
+
.chunk { |s| s.is_a?(String) }
|
205
|
+
.flat_map do |(strs, group)|
|
206
|
+
strs ? group.map { |s| s.encode("UTF-16BE") }.join : group
|
207
|
+
end
|
208
|
+
)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
module DoubleQuoteChunk
|
213
|
+
def value
|
214
|
+
if captures.key?(:double_quote_escaped)
|
215
|
+
capture(:double_quote_escaped).value
|
216
|
+
else
|
217
|
+
super
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
module DoubleQuoteEscaped
|
223
|
+
ESCAPES = {
|
224
|
+
"\"" => "\"",
|
225
|
+
"$" => "$",
|
226
|
+
"\\" => "\\",
|
227
|
+
"/" => "/",
|
228
|
+
"b" => "\b",
|
229
|
+
"f" => "\f",
|
230
|
+
"n" => "\n",
|
231
|
+
"r" => "\r",
|
232
|
+
"t" => "\t"
|
233
|
+
}.freeze
|
234
|
+
|
235
|
+
def value
|
236
|
+
ESCAPES.fetch(string) do
|
237
|
+
[string[1..-1]].pack("H*").force_encoding("UTF-16BE")
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
module SingleQuoteLiteral
|
243
|
+
def value
|
244
|
+
chunks = capture(:single_quote_continue).value
|
245
|
+
indent = chunks.join.split(/\n/, -1).map { |line|
|
246
|
+
line.match(/^( *|\t*)/).to_s.length
|
247
|
+
}.min
|
248
|
+
|
249
|
+
TextLiteral.for(
|
250
|
+
*chunks
|
251
|
+
.chunk { |c| c != "\n" }
|
252
|
+
.flat_map { |(line, chunk)| line ? chunk[indent..-1] : chunk }
|
253
|
+
)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
module SingleQuoteContinue
|
258
|
+
def value
|
259
|
+
([first].compact + captures(:single_quote_continue)).flat_map(&:value)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
module Interpolation
|
264
|
+
def value
|
265
|
+
capture(:complete_expression).value
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
module EscapedQuotePair
|
270
|
+
def value
|
271
|
+
"''"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
module EscapedInterpolation
|
276
|
+
def value
|
277
|
+
"${"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
module NonEmptyListLiteral
|
282
|
+
def value
|
283
|
+
List.new(elements: captures(:expression).map(&:value))
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
module Identifier
|
288
|
+
def value
|
289
|
+
name = capture(:any_label).value
|
290
|
+
|
291
|
+
return Dhall::Bool.new(value: true) if name == "True"
|
292
|
+
return Dhall::Bool.new(value: false) if name == "False"
|
293
|
+
|
294
|
+
Dhall::Builtins::ALL[name]&.new ||
|
295
|
+
Variable.new(
|
296
|
+
name: name,
|
297
|
+
index: capture(:natural_literal)&.string.to_i
|
298
|
+
)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
module PrimitiveExpression
|
303
|
+
def value
|
304
|
+
key = [
|
305
|
+
:complete_expression,
|
306
|
+
:record_type_or_literal,
|
307
|
+
:union_type_or_literal
|
308
|
+
].find { |k| captures.key?(k) }
|
309
|
+
key ? capture(key).value : super
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
module EmptyUnionType
|
314
|
+
def value
|
315
|
+
UnionType.new(alternatives: {})
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
module UnionTypeOrLiteralVariantType
|
320
|
+
def value(label)
|
321
|
+
rest = capture(:non_empty_union_type_or_literal)&.value
|
322
|
+
type = UnionType.new(
|
323
|
+
alternatives: { label => capture(:expression)&.value }
|
324
|
+
)
|
325
|
+
if rest.is_a?(Union)
|
326
|
+
rest.with(alternatives: type.merge(rest.alternatives))
|
327
|
+
else
|
328
|
+
rest ? type.merge(rest) : type
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
module UnionLiteralVariantValue
|
334
|
+
def value(label)
|
335
|
+
Union.new(
|
336
|
+
tag: label,
|
337
|
+
value: capture(:expression).value,
|
338
|
+
alternatives: captures(:union_type_entry).map(&:value)
|
339
|
+
.reduce(UnionType.new(alternatives: {}), &:merge)
|
340
|
+
)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
module UnionTypeEntry
|
345
|
+
def value
|
346
|
+
UnionType.new(
|
347
|
+
alternatives: {
|
348
|
+
capture(:any_label).value => capture(:expression)&.value
|
349
|
+
}
|
350
|
+
)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
module NonEmptyUnionTypeOrLiteral
|
355
|
+
def value
|
356
|
+
key = [
|
357
|
+
:union_literal_variant_value,
|
358
|
+
:union_type_or_literal_variant_type
|
359
|
+
].find { |k| captures.key?(k) }
|
360
|
+
|
361
|
+
if key
|
362
|
+
capture(key).value(capture(:any_label).value)
|
363
|
+
else
|
364
|
+
no_alts = UnionType.new(alternatives: {})
|
365
|
+
Union.from(no_alts, capture(:any_label).value, nil)
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
module EmptyRecordLiteral
|
371
|
+
def value
|
372
|
+
EmptyRecord.new
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
module EmptyRecordType
|
377
|
+
def value
|
378
|
+
Dhall::EmptyRecordType.new
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
module NonEmptyRecordTypeOrLiteral
|
383
|
+
def value
|
384
|
+
key = [
|
385
|
+
:non_empty_record_literal,
|
386
|
+
:non_empty_record_type
|
387
|
+
].find { |k| captures.key?(k) }
|
388
|
+
|
389
|
+
capture(key).value(capture(:any_label).value)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
module NonEmptyRecordLiteral
|
394
|
+
def value(first_key)
|
395
|
+
Record.new(
|
396
|
+
record: captures(:record_literal_entry).map(&:value).reduce(
|
397
|
+
first_key => capture(:expression).value
|
398
|
+
) do |final, rec|
|
399
|
+
final.merge(rec) { raise TypeError, "duplicate field" }
|
400
|
+
end
|
401
|
+
)
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
module RecordLiteralEntry
|
406
|
+
def value
|
407
|
+
{ capture(:any_label).value => capture(:expression).value }
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
module NonEmptyRecordType
|
412
|
+
def value(first_key)
|
413
|
+
RecordType.new(
|
414
|
+
record: captures(:record_type_entry).map(&:value).reduce(
|
415
|
+
{ first_key => capture(:expression).value },
|
416
|
+
&:merge
|
417
|
+
)
|
418
|
+
)
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
RecordTypeEntry = RecordLiteralEntry
|
423
|
+
|
424
|
+
module EmptyCollection
|
425
|
+
def value
|
426
|
+
if captures.key?(:list)
|
427
|
+
EmptyList.new(element_type: capture(:import_expression).value)
|
428
|
+
else
|
429
|
+
OptionalNone.new(value_type: capture(:import_expression).value)
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
module NonEmptyOptional
|
435
|
+
def value
|
436
|
+
Optional.new(
|
437
|
+
value: capture(:expression).value,
|
438
|
+
value_type: capture(:import_expression).value
|
439
|
+
)
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
module AnnotatedExpression
|
444
|
+
def value
|
445
|
+
if matches[1].string.empty?
|
446
|
+
first.value
|
447
|
+
else
|
448
|
+
TypeAnnotation.new(
|
449
|
+
value: first.value,
|
450
|
+
type: capture(:expression).value
|
451
|
+
)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
module LetBinding
|
457
|
+
def value
|
458
|
+
exprs = captures(:expression)
|
459
|
+
Let.new(
|
460
|
+
var: capture(:nonreserved_label).value,
|
461
|
+
assign: exprs.last.value,
|
462
|
+
type: exprs.length > 1 ? exprs.first.value : nil
|
463
|
+
)
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
module Import
|
468
|
+
def value
|
469
|
+
import_type = if captures.key?(:text)
|
470
|
+
Dhall::Import::Text
|
471
|
+
else
|
472
|
+
Dhall::Import::Expression
|
473
|
+
end
|
474
|
+
|
475
|
+
capture(:import_hashed).value(import_type)
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
module ImportHashed
|
480
|
+
def value(import_type)
|
481
|
+
integrity_check = capture(:hash)&.value
|
482
|
+
path = capture(:import_type).value
|
483
|
+
Dhall::Import.new(integrity_check, import_type, path)
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
module Hash
|
488
|
+
def value
|
489
|
+
protocol, data = string.split(/:/, 2)
|
490
|
+
Dhall::Import::IntegrityCheck.new(protocol, data)
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
module Http
|
495
|
+
SCHEME = {
|
496
|
+
"http" => Dhall::Import::Http,
|
497
|
+
"https" => Dhall::Import::Https
|
498
|
+
}.freeze
|
499
|
+
|
500
|
+
def value
|
501
|
+
http = capture(:http_raw)
|
502
|
+
SCHEME.fetch(http.capture(:scheme).value).new(
|
503
|
+
capture(:import_hashed)&.value(Dhall::Import::Expression),
|
504
|
+
http.capture(:authority).value,
|
505
|
+
*unescaped_components,
|
506
|
+
http.capture(:query)&.value
|
507
|
+
)
|
508
|
+
end
|
509
|
+
|
510
|
+
def unescaped_components
|
511
|
+
capture(:http_raw)
|
512
|
+
.capture(:path)
|
513
|
+
.captures(:path_component)
|
514
|
+
.map do |pc|
|
515
|
+
pc.value(URI.method(:unescape))
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
module Env
|
521
|
+
def value
|
522
|
+
Dhall::Import::EnvironmentVariable.new(
|
523
|
+
if captures.key?(:bash_environment_variable)
|
524
|
+
capture(:bash_environment_variable).string
|
525
|
+
else
|
526
|
+
capture(:posix_environment_variable).value.encode("utf-8")
|
527
|
+
end
|
528
|
+
)
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
module PosixEnvironmentVariable
|
533
|
+
def value
|
534
|
+
matches.map(&:value).join
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
module PosixEnvironmentVariableCharacter
|
539
|
+
ESCAPES = Dhall::Import::EnvironmentVariable::ESCAPES
|
540
|
+
|
541
|
+
def value
|
542
|
+
if first&.string == "\\"
|
543
|
+
ESCAPES.fetch(matches[1].string) {
|
544
|
+
raise "Invalid escape: #{string}"
|
545
|
+
}.encode("UTF-16BE")
|
546
|
+
else
|
547
|
+
string
|
548
|
+
end
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
module AbsolutePath
|
553
|
+
def value
|
554
|
+
Dhall::Import::AbsolutePath.new(*super)
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
module HerePath
|
559
|
+
def value
|
560
|
+
Dhall::Import::RelativePath.new(*capture(:path).value)
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
module ParentPath
|
565
|
+
def value
|
566
|
+
Dhall::Import::RelativeToParentPath.new(*capture(:path).value)
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
module HomePath
|
571
|
+
def value
|
572
|
+
Dhall::Import::RelativeToHomePath.new(*capture(:path).value)
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
module Path
|
577
|
+
def value
|
578
|
+
captures(:path_component).map(&:value)
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
module PathComponent
|
583
|
+
def value(unescaper=:itself.to_proc)
|
584
|
+
if captures.key?(:quoted_path_component)
|
585
|
+
capture(:quoted_path_component).value
|
586
|
+
else
|
587
|
+
unescaper.call(capture(:unquoted_path_component).value)
|
588
|
+
end
|
589
|
+
end
|
590
|
+
end
|
591
|
+
|
592
|
+
module Missing
|
593
|
+
def value
|
594
|
+
Dhall::Import::MissingImport.new
|
595
|
+
end
|
596
|
+
end
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
require "citrus"
|
601
|
+
Citrus.require "dhall/parser"
|