dhall 0.2.0 → 0.3.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
  SHA1:
3
- metadata.gz: 2189303de0238086f008d9fa07f0a24de1f0d825
4
- data.tar.gz: 33dd23d2bcffb5e1171a7a0a08320c02be502fe1
3
+ metadata.gz: 30ff6e0ad066b23996b9a31b21e7f1a30b86b265
4
+ data.tar.gz: 88efb36ee05894a74df3b691d67774b8e0eea185
5
5
  SHA512:
6
- metadata.gz: 3e3385fa51023333875319b5355a8eb11dcb9e69b0bbaa6b2bbf368003ea46a79daf7819dfcc9e8e444f028e6edbbf33dba8a3e68043a2a92a97767f82819bca
7
- data.tar.gz: d690a7f50b99ee3d8f7a16ab4e7439c42b1500cc23186e30a6db73c8c76af3ca452c9febc303f7ce179166cc55629188720a1ea9839469c3c29356559a25d2d6
6
+ metadata.gz: ff197049f1d4ceb3e1742a4e3b44491192170c4bb1e32b7aef6bbaeb643f16b828ffdee1d78eaa628a9f0dd9d5dedd0f4c796556b2e72b92ed0705a0e8306430
7
+ data.tar.gz: e92aad6b3921f4bd25ebdf8f20204b95ceec8814250ed90b5424b70a75991ca68de8bd60b0c2cf982541c560f9e1ba135f87f0b05f685d767588150b051c465f
data/README.md CHANGED
@@ -12,7 +12,7 @@ For the purposes of considering what is a "breaking change" only the API as docu
12
12
 
13
13
  Add this line to your application's Gemfile:
14
14
 
15
- gem 'dhall'
15
+ gem "dhall"
16
16
 
17
17
  And then execute:
18
18
 
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "dhall"
5
+ require "optparse"
6
+
7
+ @extension = ".dhallb"
8
+
9
+ def compile(source)
10
+ Dhall.load(
11
+ source,
12
+ timeout: Float::INFINITY,
13
+ resolver: Dhall::Resolvers::Default.new(
14
+ max_depth: Float::INFINITY
15
+ )
16
+ ).then(&:to_binary)
17
+ end
18
+
19
+ def compile_file(file_path, relative_to: Pathname.new("."))
20
+ out = file_path.sub_ext(@extension)
21
+ if @output_directory
22
+ out = @output_directory + out.relative_path_from(relative_to)
23
+ out.dirname.mkpath
24
+ end
25
+ warn "#{file_path} => #{out}"
26
+ compile(file_path.expand_path).then(&out.method(:write))
27
+ end
28
+
29
+ opt_parser = OptionParser.new do |opts|
30
+ opts.banner = "Usage: dhall-compile [options] [-] [files_and_dirs]"
31
+
32
+ opts.on(
33
+ "-oDIRECTORY",
34
+ "--output-directory DIRECTORY",
35
+ "Write output to this directory"
36
+ ) do |dir|
37
+ @output_directory = Pathname.new(dir)
38
+ end
39
+
40
+ opts.on(
41
+ "-e [EXTENSION]",
42
+ "--extension [EXTENSION]",
43
+ "Use this extension for files (default .dhallb)"
44
+ ) do |ext|
45
+ @extension = ext ? ".#{ext}" : ""
46
+ end
47
+
48
+ opts.on("-h", "--help", "Show this usage information") do
49
+ warn opts
50
+ exit
51
+ end
52
+ end
53
+
54
+ opt_parser.parse!
55
+
56
+ if ARGV.empty?
57
+ warn opt_parser
58
+ exit 0
59
+ end
60
+
61
+ ARGV.map(&Pathname.method(:new)).each do |path|
62
+ if !path.exist? && path.to_s == "-"
63
+ warn "Compiling STDIN to STDOUT"
64
+ compile(STDIN.read).then(&STDOUT.method(:write)).sync
65
+ elsif path.file?
66
+ compile_file(path, relative_to: path.dirname).sync
67
+ elsif path.directory?
68
+ warn "Recursively compiling #{path}"
69
+ path.find.flat_map do |child|
70
+ next if !child.file? || child.extname == ".dhallb"
71
+ compile_file(child, relative_to: path).sync
72
+ end
73
+ else
74
+ warn "#{path} may not exist"
75
+ exit 1
76
+ end
77
+ end
@@ -6,31 +6,84 @@ require "psych"
6
6
  module Dhall
7
7
  module AsDhall
8
8
  TAGS = {
9
- ::Integer => "Integer",
9
+ ::Array => "List",
10
10
  ::FalseClass => "Bool",
11
- ::Integer => "Integer",
12
11
  ::Float => "Double",
12
+ ::Hash => "Record",
13
+ ::Integer => "Integer",
14
+ ::Integer => "Integer",
13
15
  ::NilClass => "None",
14
16
  ::String => "Text",
15
17
  ::TrueClass => "Bool"
16
18
  }.freeze
17
19
 
18
- def self.tag_for(o, type)
20
+ def self.tag_for(o)
19
21
  return "Natural" if o.is_a?(::Integer) && !o.negative?
20
22
 
21
23
  TAGS.fetch(o.class) do
22
- "#{o.class.name}_#{type.digest.hexdigest}"
24
+ o.class.name
25
+ end
26
+ end
27
+
28
+ class AnnotatedExpressionList
29
+ attr_reader :type
30
+ attr_reader :exprs
31
+
32
+ def self.from(type_annotation)
33
+ if type_annotation.nil?
34
+ new(nil, [nil])
35
+ else
36
+ new(type_annotation.type, [type_annotation.value])
37
+ end
38
+ end
39
+
40
+ def initialize(type, exprs)
41
+ @type = type
42
+ @exprs = exprs
43
+ end
44
+
45
+ def +(other)
46
+ raise "#{type} != #{other.type}" if type != other.type
47
+ self.class.new(type, exprs + other.exprs)
23
48
  end
24
49
  end
25
50
 
26
- def self.union_of(values_and_types)
27
- z = [UnionType.new(alternatives: {}), []]
28
- values_and_types.reduce(z) do |(ut, tags), (v, t)|
29
- tag = tag_for(v, t)
30
- [
31
- ut.merge(UnionType.new(alternatives: { tag => t })),
32
- tags + [tag]
33
- ]
51
+ class UnionInferer
52
+ def initialize(tagged={})
53
+ @tagged = tagged
54
+ end
55
+
56
+ def union_type
57
+ UnionType.new(alternatives: Hash[@tagged.map { |k, v| [k, v.type] }])
58
+ end
59
+
60
+ def union_for(expr)
61
+ if expr.is_a?(Enum)
62
+ tag = expr.tag
63
+ expr = nil
64
+ else
65
+ tag = @tagged.keys.find { |k| @tagged[k].exprs.include?(expr) }
66
+ end
67
+ expr = expr.extract if expr.is_a?(Union)
68
+ Union.from(union_type, tag, expr)
69
+ end
70
+
71
+ def with(tag, type_annotation)
72
+ anno = AnnotatedExpressionList.from(type_annotation)
73
+ if @tagged.key?(tag) && @tagged[tag].type != anno.type
74
+ disambiguate_against(tag, anno)
75
+ else
76
+ self.class.new(@tagged.merge(tag => anno) { |_, x, y| x + y })
77
+ end
78
+ end
79
+
80
+ def disambiguate_against(tag, anno)
81
+ self.class.new(
82
+ @tagged.reject { |k, _| k == tag }.merge(
83
+ "#{tag}_#{@tagged[tag].type.digest.hexdigest}" => @tagged[tag],
84
+ "#{tag}_#{anno.type.digest.hexdigest}" => anno
85
+ )
86
+ )
34
87
  end
35
88
  end
36
89
 
@@ -46,9 +99,8 @@ module Dhall
46
99
 
47
100
  refine ::Symbol do
48
101
  def as_dhall
49
- Dhall::Union.new(
102
+ Dhall::Enum.new(
50
103
  tag: to_s,
51
- value: nil,
52
104
  alternatives: Dhall::UnionType.new(alternatives: {})
53
105
  )
54
106
  end
@@ -143,17 +195,28 @@ module Dhall
143
195
 
144
196
  class Union
145
197
  def initialize(values, exprs, types)
146
- @values = values
198
+ @tags, @types = values.zip(types).map { |(value, type)|
199
+ if type.is_a?(UnionType) && type.alternatives.length == 1
200
+ type.alternatives.to_a.first
201
+ else
202
+ [AsDhall.tag_for(value), type]
203
+ end
204
+ }.transpose
147
205
  @exprs = exprs
148
- @types = types
206
+ @inferer = UnionInferer.new
149
207
  end
150
208
 
151
209
  def list
152
- ut, tags = AsDhall.union_of(@values.zip(@types))
153
-
154
- List.new(elements: @exprs.zip(tags).map do |(expr, tag)|
155
- Dhall::Union.from(ut, tag, expr)
156
- end)
210
+ final_inferer =
211
+ @tags
212
+ .zip(@exprs, @types)
213
+ .reduce(@inferer) do |inferer, (tag, expr, type)|
214
+ inferer.with(
215
+ tag,
216
+ type.nil? ? nil : TypeAnnotation.new(value: expr, type: type)
217
+ )
218
+ end
219
+ List.new(elements: @exprs.map(&final_inferer.method(:union_for)))
157
220
  end
158
221
  end
159
222
  end
@@ -215,5 +278,11 @@ module Dhall
215
278
  )
216
279
  end
217
280
  end
281
+
282
+ refine ::Proc do
283
+ def as_dhall
284
+ FunctionProxy.new(self)
285
+ end
286
+ end
218
287
  end
219
288
  end
@@ -102,6 +102,10 @@ module Dhall
102
102
  end
103
103
  end
104
104
 
105
+ def to_s
106
+ inspect
107
+ end
108
+
105
109
  def as_dhall
106
110
  self
107
111
  end
@@ -157,7 +161,8 @@ module Dhall
157
161
  end
158
162
  end
159
163
 
160
- def call(*args)
164
+ def call(*args, &block)
165
+ args += [block] if block
161
166
  args.map! { |arg| arg&.as_dhall }
162
167
  return super if args.length > 1
163
168
 
@@ -168,6 +173,29 @@ module Dhall
168
173
  end
169
174
 
170
175
  alias [] call
176
+ alias === call
177
+
178
+ def <<(other)
179
+ FunctionProxy.new(
180
+ ->(*args, &block) { call(other.call(*args, &block)) },
181
+ curry: false
182
+ )
183
+ end
184
+
185
+ def >>(other)
186
+ FunctionProxy.new(
187
+ ->(*args, &block) { other.call(call(*args, &block)) },
188
+ curry: false
189
+ )
190
+ end
191
+
192
+ def binding
193
+ to_proc.binding
194
+ end
195
+
196
+ def curry
197
+ self
198
+ end
171
199
 
172
200
  def as_json
173
201
  if var == "_"
@@ -188,6 +216,28 @@ module Dhall
188
216
  end
189
217
  end
190
218
 
219
+ class FunctionProxy < Function
220
+ def initialize(callable, curry: true)
221
+ @callable = if !curry
222
+ callable
223
+ elsif callable.respond_to?(:curry)
224
+ callable.curry
225
+ elsif callable.respond_to?(:to_proc)
226
+ callable.to_proc.curry
227
+ else
228
+ callable.method(:call).to_proc.curry
229
+ end
230
+ end
231
+
232
+ def call(*args, &block)
233
+ @callable.call(*args.map { |arg| arg&.as_dhall }, &block).as_dhall
234
+ end
235
+
236
+ def as_json
237
+ raise "Cannot serialize #{self}"
238
+ end
239
+ end
240
+
191
241
  class Bool < Expression
192
242
  include(ValueSemantics.for_attributes do
193
243
  value Bool()
@@ -764,9 +814,22 @@ module Dhall
764
814
 
765
815
  class UnionType < Expression
766
816
  include(ValueSemantics.for_attributes do
767
- alternatives Util::HashOf.new(::String, Either(Expression, nil))
817
+ alternatives Util::HashOf.new(::String, Either(Expression, nil)), default: {}
768
818
  end)
769
819
 
820
+ def empty?
821
+ alternatives.empty?
822
+ end
823
+
824
+ def [](k)
825
+ alternatives.fetch(k)
826
+ end
827
+
828
+ def without(*keys)
829
+ keys.map!(&:to_s)
830
+ with(alternatives: alternatives.reject { |k, _| keys.include?(k) })
831
+ end
832
+
770
833
  def record
771
834
  alternatives
772
835
  end
@@ -779,8 +842,8 @@ module Dhall
779
842
  self == other
780
843
  end
781
844
 
782
- def merge(other)
783
- with(alternatives: alternatives.merge(other.alternatives))
845
+ def merge(other, &block)
846
+ with(alternatives: alternatives.merge(other.alternatives, &block))
784
847
  end
785
848
 
786
849
  def fetch(k, default=nil)
@@ -817,31 +880,28 @@ module Dhall
817
880
  class Union < Expression
818
881
  include(ValueSemantics.for_attributes do
819
882
  tag ::String
820
- value Either(Expression, nil)
883
+ value Expression
821
884
  alternatives UnionType
822
885
  end)
823
886
 
824
887
  def self.from(alts, tag, value)
825
- new(
826
- tag: tag,
827
- value: value && TypeAnnotation.new(
828
- value: value,
829
- type: alts.alternatives[tag]
830
- ),
831
- alternatives: alts.with(
832
- alternatives: alts.alternatives.reject { |alt, _| alt == tag }
888
+ if value.nil?
889
+ Enum.new(tag: tag, alternatives: alts.without(tag))
890
+ else
891
+ new(
892
+ tag: tag,
893
+ value: TypeAnnotation.new(value: value, type: alts[tag]),
894
+ alternatives: alts.without(tag)
833
895
  )
834
- )
896
+ end
835
897
  end
836
898
 
837
899
  def to_s
838
- value.nil? ? tag : extract.to_s
900
+ extract.to_s
839
901
  end
840
902
 
841
903
  def extract
842
- if value.nil?
843
- tag.to_sym
844
- elsif value.is_a?(TypeAnnotation)
904
+ if value.is_a?(TypeAnnotation)
845
905
  value.value
846
906
  else
847
907
  value
@@ -851,12 +911,8 @@ module Dhall
851
911
  def reduce(handlers)
852
912
  handlers = handlers.to_h
853
913
  handler = handlers.fetch(tag.to_sym) { handlers.fetch(tag) }
854
- if value.nil?
855
- handler
856
- else
857
- (handler.respond_to?(:to_proc) ? handler.to_proc : handler)
858
- .call(extract)
859
- end
914
+ (handler.respond_to?(:to_proc) ? handler.to_proc : handler)
915
+ .call(extract)
860
916
  end
861
917
 
862
918
  def selection_syntax
@@ -869,18 +925,14 @@ module Dhall
869
925
  end
870
926
 
871
927
  def syntax
872
- if value.nil?
873
- selection_syntax
874
- else
875
- Application.new(
876
- function: selection_syntax,
877
- argument: value.is_a?(TypeAnnotation) ? value.value : value
878
- )
879
- end
928
+ Application.new(
929
+ function: selection_syntax,
930
+ argument: value.is_a?(TypeAnnotation) ? value.value : value
931
+ )
880
932
  end
881
933
 
882
934
  def as_json
883
- if value.nil? || value.respond_to?(:type)
935
+ if value.respond_to?(:type)
884
936
  syntax.as_json
885
937
  else
886
938
  [12, tag, value&.as_json, alternatives.as_json.last]
@@ -888,6 +940,31 @@ module Dhall
888
940
  end
889
941
  end
890
942
 
943
+ class Enum < Union
944
+ include(ValueSemantics.for_attributes do
945
+ tag ::String
946
+ alternatives UnionType
947
+ end)
948
+
949
+ def reduce(handlers)
950
+ handlers = handlers.to_h
951
+ handler = handlers.fetch(tag.to_sym) { handlers.fetch(tag) }
952
+ handler
953
+ end
954
+
955
+ def to_s
956
+ tag
957
+ end
958
+
959
+ def extract
960
+ tag.to_sym
961
+ end
962
+
963
+ def as_json
964
+ selection_syntax.as_json
965
+ end
966
+ end
967
+
891
968
  class If < Expression
892
969
  include(ValueSemantics.for_attributes do
893
970
  predicate Expression
@@ -1058,12 +1135,12 @@ module Dhall
1058
1135
  Builtins[:Text]
1059
1136
  end
1060
1137
 
1138
+ def empty?
1139
+ value.empty?
1140
+ end
1141
+
1061
1142
  def <<(other)
1062
- if other.is_a?(Text)
1063
- with(value: value + other.value)
1064
- else
1065
- super
1066
- end
1143
+ with(value: value + other.value)
1067
1144
  end
1068
1145
 
1069
1146
  def to_s
@@ -1096,6 +1173,14 @@ module Dhall
1096
1173
  fixed.length == 1 ? fixed.first : new(chunks: fixed)
1097
1174
  end
1098
1175
 
1176
+ def start_empty?
1177
+ chunks.first.empty?
1178
+ end
1179
+
1180
+ def end_empty?
1181
+ chunks.last.empty?
1182
+ end
1183
+
1099
1184
  def as_json
1100
1185
  [18, *chunks.map { |chunk| chunk.is_a?(Text) ? chunk.value : chunk.as_json }]
1101
1186
  end
@@ -1343,24 +1428,6 @@ module Dhall
1343
1428
  end
1344
1429
 
1345
1430
  class EnvironmentVariable
1346
- ESCAPES = {
1347
- "\"" => "\"",
1348
- "\\" => "\\",
1349
- "a" => "\a",
1350
- "b" => "\b",
1351
- "f" => "\f",
1352
- "n" => "\n",
1353
- "r" => "\r",
1354
- "t" => "\t",
1355
- "v" => "\v"
1356
- }.freeze
1357
-
1358
- def self.decode(var)
1359
- var.gsub(/\\[\"\\abfnrtv]/) do |escape|
1360
- ESCAPES.fetch(escape[1])
1361
- end
1362
- end
1363
-
1364
1431
  attr_reader :var
1365
1432
 
1366
1433
  def initialize(var)
@@ -1400,7 +1467,10 @@ module Dhall
1400
1467
  end
1401
1468
 
1402
1469
  def to_s
1403
- "env:#{as_json}"
1470
+ escapes = Parser::PosixEnvironmentVariableCharacter::ESCAPES
1471
+ "env:#{@var.gsub(/[\"\\\a\b\f\n\r\t\v]/) do |c|
1472
+ "\\" + escapes.find { |(_, v)| v == c }.first
1473
+ end}"
1404
1474
  end
1405
1475
 
1406
1476
  def hash
@@ -1413,9 +1483,7 @@ module Dhall
1413
1483
  alias eql? ==
1414
1484
 
1415
1485
  def as_json
1416
- @var.gsub(/[\"\\\a\b\f\n\r\t\v]/) do |c|
1417
- "\\" + ESCAPES.find { |(_, v)| v == c }.first
1418
- end
1486
+ @var
1419
1487
  end
1420
1488
  end
1421
1489
 
@@ -1535,6 +1603,19 @@ module Dhall
1535
1603
  body Expression
1536
1604
  end)
1537
1605
 
1606
+ def lets
1607
+ [let]
1608
+ end
1609
+
1610
+ def flatten
1611
+ flattened = body.is_a?(LetIn) ? body.flatten : body
1612
+ if flattened.is_a?(LetIn) || flattened.is_a?(LetBlock)
1613
+ LetBlock.for(lets: [let] + flattened.lets, body: flattened.body)
1614
+ else
1615
+ self
1616
+ end
1617
+ end
1618
+
1538
1619
  def desugar
1539
1620
  Application.new(
1540
1621
  function: Function.new(
@@ -1572,6 +1653,10 @@ module Dhall
1572
1653
  end
1573
1654
  end
1574
1655
 
1656
+ def flatten
1657
+ unflatten.flatten
1658
+ end
1659
+
1575
1660
  def unflatten
1576
1661
  lets.reverse.reduce(body) do |inside, let|
1577
1662
  letin = LetIn.new(let: let, body: inside)
@@ -192,7 +192,6 @@ module Dhall
192
192
  class Import
193
193
  def self.decode(integrity_check, import_type, path_type, *parts)
194
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
195
 
197
196
  new(
198
197
  IntegrityCheck.new(*integrity_check),
@@ -223,6 +222,13 @@ module Dhall
223
222
  end
224
223
  end
225
224
 
225
+ def self.handle_tag(e)
226
+ return e unless e.is_a?(::CBOR::Tagged)
227
+ return e.value if e.tag == 55799
228
+
229
+ raise "Unknown tag: #{e.inspect}"
230
+ end
231
+
226
232
  BINARY = {
227
233
  ::TrueClass => ->(e) { Bool.new(value: e) },
228
234
  ::FalseClass => ->(e) { Bool.new(value: e) },
@@ -230,6 +236,7 @@ module Dhall
230
236
  ::String => ->(e) { Builtins[e.to_sym] || (raise "Unknown builtin") },
231
237
  ::Integer => ->(e) { Variable.new(index: e) },
232
238
  ::Array => lambda { |e|
239
+ e = e.map(&method(:handle_tag))
233
240
  if e.length == 2 && e.first.is_a?(::String)
234
241
  Variable.new(name: e[0], index: e[1])
235
242
  else
@@ -238,11 +245,7 @@ module Dhall
238
245
  (raise "Unknown expression: #{e.inspect}")
239
246
  end
240
247
  },
241
- ::CBOR::Tagged => lambda { |e|
242
- return Dhall.decode(e.value) if e.tag == 55799
243
-
244
- raise "Unknown tag: #{e.inspect}"
245
- }
248
+ ::CBOR::Tagged => ->(e) { Dhall.decode(handle_tag(e)) }
246
249
  }.freeze
247
250
 
248
251
  BINARY_TAGS = [
@@ -117,17 +117,22 @@ module Dhall
117
117
 
118
118
  refine Function do
119
119
  def to_ruby(&decode)
120
- ->(*args) { decode[expr.call(*args)] }
120
+ ->(*args) { decode[call(*args)] }
121
+ end
122
+ end
123
+
124
+ refine Enum do
125
+ def to_ruby
126
+ extract == :None ? nil : extract
121
127
  end
122
128
  end
123
129
 
124
130
  refine Union do
125
131
  def to_ruby
126
- if !value.nil? && tag.match(/\A\p{Upper}/) &&
127
- Object.const_defined?(tag)
128
- yield extract, Object.const_get(tag)
129
- elsif extract == :None
130
- nil
132
+ rtag = tag.sub(/_[0-9a-f]{64}\Z/, "")
133
+ if tag.match(/\A\p{Upper}/) &&
134
+ Object.const_defined?(rtag) && !Dhall.const_defined?(rtag, false)
135
+ yield extract, Object.const_get(rtag)
131
136
  else
132
137
  yield extract
133
138
  end
@@ -120,9 +120,18 @@ module Dhall
120
120
  end
121
121
  end
122
122
 
123
- class Forall; end
123
+ class FunctionProxy
124
+ def shift(*)
125
+ self
126
+ end
127
+
128
+ def substitute(*)
129
+ raise "Cannot substitute #{self}"
130
+ end
124
131
 
125
- class Bool
132
+ def normalize
133
+ self
134
+ end
126
135
  end
127
136
 
128
137
  class Variable
@@ -335,6 +344,12 @@ module Dhall
335
344
  end
336
345
  end
337
346
 
347
+ class Enum
348
+ def normalize
349
+ with(alternatives: alternatives.normalize)
350
+ end
351
+ end
352
+
338
353
  class If
339
354
  def normalize
340
355
  normalized = super
@@ -357,7 +372,14 @@ module Dhall
357
372
 
358
373
  class TextLiteral
359
374
  def normalize
360
- TextLiteral.for(*super.flatten.chunks)
375
+ lit = TextLiteral.for(*super.flatten.chunks)
376
+
377
+ if lit.is_a?(TextLiteral) && lit.chunks.length == 3 &&
378
+ lit.start_empty? && lit.end_empty?
379
+ lit.chunks[1]
380
+ else
381
+ lit
382
+ end
361
383
  end
362
384
 
363
385
  def flatten
@@ -380,6 +402,17 @@ module Dhall
380
402
  body: body.shift(amount, name, min_index + 1)
381
403
  )
382
404
  end
405
+
406
+ def substitute(svar, with_expr)
407
+ var = let.var
408
+ with(
409
+ let: let.substitute(svar, with_expr),
410
+ body: body.substitute(
411
+ var == svar.name ? svar.with(index: svar.index + 1) : svar,
412
+ with_expr.shift(1, var, 0)
413
+ )
414
+ )
415
+ end
383
416
  end
384
417
 
385
418
  class LetBlock
@@ -390,6 +423,10 @@ module Dhall
390
423
  def shift(amount, name, min_index)
391
424
  unflatten.shift(amount, name, min_index)
392
425
  end
426
+
427
+ def substitute(svar, with_expr)
428
+ unflatten.substitute(svar, with_expr)
429
+ end
393
430
  end
394
431
 
395
432
  class TypeAnnotation
@@ -10,10 +10,10 @@ rule block_comment
10
10
  (/(?:\u{7b})(?:\u{2d})/i (block_comment_continue))
11
11
  end
12
12
  rule block_comment_char
13
- (/[\u{20}-@\u{5b}-\u{10ffff}]/i | (tab) | (end_of_line))
13
+ !("{-" | "-}") (/[\u{20}-@\u{5b}-\u{10ffff}]/i | (tab) | (end_of_line))
14
14
  end
15
15
  rule block_comment_continue
16
- ((/(?:\u{2d})(?:\u{7d})/i) | ((block_comment) (block_comment_continue)) | ((block_comment_char) (block_comment_continue)))
16
+ ("-}" | block_comment_char+ block_comment_continue | (block_comment block_comment_continue))
17
17
  end
18
18
  rule not_end_of_line
19
19
  (/[\u{20}-@\u{5b}-\u{10ffff}]/i | (tab))
@@ -340,7 +340,7 @@ rule ipvfuture
340
340
  (`v` ((hexdig)+) /\u{2e}/i (((unreserved) | (sub_delims) | `:`)+))
341
341
  end
342
342
  rule ipv6address
343
- (((((h16) `:`) 6*6) (ls32)) | (`::` (((h16) `:`) 5*5) (ls32)) | (((h16)?) `::` (((h16) `:`) 4*4) (ls32)) | ((((((h16) `:`)?) (h16))?) `::` (((h16) `:`) 3*3) (ls32)) | ((((((h16) `:`) 0*2) (h16))?) `::` (((h16) `:`) 2*2) (ls32)) | ((((((h16) `:`) 0*3) (h16))?) `::` (h16) `:` (ls32)) | ((((((h16) `:`) 0*4) (h16))?) `::` (ls32)) | ((((((h16) `:`) 0*5) (h16))?) `::` (h16)) | ((((((h16) `:`) 0*6) (h16))?) `::`))
343
+ (((((h16) `:`) 6*6) (ls32)) | (`::` (((h16) `:`) 5*5) (ls32)) | (((h16)?) `::` (((h16) `:`) 4*4) (ls32)) | ((((h16) ((`:` (h16))?))?) `::` (((h16) `:`) 3*3) (ls32)) | ((((h16) ((`:` (h16)) 0*2))?) `::` (((h16) `:`) 2*2) (ls32)) | ((((h16) ((`:` (h16)) 0*3))?) `::` (h16) `:` (ls32)) | ((((h16) ((`:` (h16)) 0*4))?) `::` (ls32)) | ((((h16) ((`:` (h16)) 0*5))?) `::` (h16)) | ((((h16) ((`:` (h16)) 0*6))?) `::`))
344
344
  end
345
345
  rule h16
346
346
  ((hexdig) 1*4)
@@ -352,7 +352,7 @@ rule ipv4address
352
352
  ((dec_octet) /\u{2e}/i (dec_octet) /\u{2e}/i (dec_octet) /\u{2e}/i (dec_octet))
353
353
  end
354
354
  rule dec_octet
355
- ((digit) | (/[1-9]/i (digit)) | (`1` ((digit) 2*2)) | (/2(?:[0-4])/i (digit)) | (/25(?:[0-5])/i))
355
+ ((/25(?:[0-5])/i) | (/2(?:[0-4])/i (digit)) | (`1` ((digit) 2*2)) | (/[1-9]/i (digit)) | (digit))
356
356
  end
357
357
  rule reg_name
358
358
  (((unreserved) | (pct_encoded) | (sub_delims))*)
@@ -276,14 +276,15 @@ module Dhall
276
276
  module SingleQuoteLiteral
277
277
  def value
278
278
  chunks = capture(:single_quote_continue).value
279
- indent = chunks.join.split(/\n/, -1).map { |line|
280
- line.match(/^( *|\t*)/).to_s.length
281
- }.min
279
+ raw = chunks.join
280
+ indent = raw.scan(/^[ \t]*(?=[^ \t\r\n])/).map(&:chars)
281
+ .reduce(&Util.method(:longest_common_prefix)).length
282
+ indent = 0 if raw.end_with?("\n")
282
283
 
283
284
  TextLiteral.for(
284
285
  *chunks
285
- .chunk { |c| c != "\n" }
286
- .flat_map { |(line, chunk)| line ? chunk[indent..-1] : chunk }
286
+ .chunk { |c| c != "\n" }
287
+ .flat_map { |(line, chunk)| line ? chunk[indent..-1] : chunk }
287
288
  )
288
289
  end
289
290
  end
@@ -558,9 +559,9 @@ module Dhall
558
559
  def value
559
560
  Dhall::Import::EnvironmentVariable.new(
560
561
  if captures.key?(:bash_environment_variable)
561
- capture(:bash_environment_variable).string
562
+ capture(:bash_environment_variable).value
562
563
  else
563
- capture(:posix_environment_variable).value.encode("utf-8")
564
+ capture(:posix_environment_variable).value
564
565
  end
565
566
  )
566
567
  end
@@ -568,12 +569,22 @@ module Dhall
568
569
 
569
570
  module PosixEnvironmentVariable
570
571
  def value
571
- matches.map(&:value).join
572
+ matches.map(&:value).join.encode(Encoding::UTF_8)
572
573
  end
573
574
  end
574
575
 
575
576
  module PosixEnvironmentVariableCharacter
576
- ESCAPES = Dhall::Import::EnvironmentVariable::ESCAPES
577
+ ESCAPES = {
578
+ "\"" => "\"",
579
+ "\\" => "\\",
580
+ "a" => "\a",
581
+ "b" => "\b",
582
+ "f" => "\f",
583
+ "n" => "\n",
584
+ "r" => "\r",
585
+ "t" => "\t",
586
+ "v" => "\v"
587
+ }.freeze
577
588
 
578
589
  def value
579
590
  if first&.string == "\\"
@@ -388,11 +388,11 @@ module Dhall
388
388
  end
389
389
 
390
390
  def element_type
391
- @alist.first.value&.type || @alist.element_type
391
+ (@alist.first.value&.type || @alist.element_type).normalize
392
392
  end
393
393
 
394
394
  def element_types
395
- @alist.to_a.map(&:type)
395
+ @alist.to_a.map(&:type).map(&:normalize)
396
396
  end
397
397
  end
398
398
 
@@ -604,6 +604,25 @@ module Dhall
604
604
  end
605
605
  end
606
606
 
607
+ class Enum
608
+ TypeChecker.register self, Dhall::Enum
609
+
610
+ def initialize(enum)
611
+ @enum = enum
612
+ end
613
+
614
+ def annotate(context)
615
+ type = Dhall::UnionType.new(
616
+ alternatives: { @enum.tag => nil }
617
+ ).merge(@enum.alternatives)
618
+
619
+ # Annotate to sanity check
620
+ TypeChecker.for(type).annotate(context)
621
+
622
+ Dhall::TypeAnnotation.new(value: @enum, type: type)
623
+ end
624
+ end
625
+
607
626
  class Union
608
627
  TypeChecker.register self, Dhall::Union
609
628
 
@@ -1189,6 +1208,15 @@ module Dhall
1189
1208
  class Builtin
1190
1209
  TypeChecker.register self, Dhall::Builtin
1191
1210
 
1211
+ def self.for(builtin)
1212
+ unfilled = builtin.unfill
1213
+ if unfilled != builtin
1214
+ TypeChecker.for(unfilled)
1215
+ else
1216
+ new(builtin)
1217
+ end
1218
+ end
1219
+
1192
1220
  def initialize(builtin)
1193
1221
  @expr = builtin
1194
1222
  @name = builtin.as_json
@@ -177,5 +177,9 @@ module Dhall
177
177
  str.encode(Encoding::UTF_8)
178
178
  end
179
179
  end
180
+
181
+ def self.longest_common_prefix(a, b)
182
+ a.zip(b).take_while { |(x, y)| x == y }.map(&:first)
183
+ end
180
184
  end
181
185
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dhall
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Paul Weber
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-29 00:00:00.000000000 Z
11
+ date: 2019-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cbor
@@ -114,6 +114,7 @@ description: 'This is a Ruby implementation of the Dhall configuration language.
114
114
  email:
115
115
  - dev@singpolyma.net
116
116
  executables:
117
+ - dhall-compile
117
118
  - json-to-dhall
118
119
  - yaml-to-dhall
119
120
  extensions: []
@@ -121,6 +122,7 @@ extra_rdoc_files: []
121
122
  files:
122
123
  - COPYING
123
124
  - README.md
125
+ - bin/dhall-compile
124
126
  - bin/json-to-dhall
125
127
  - bin/yaml-to-dhall
126
128
  - dhall.gemspec