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,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"