dhall 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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