M lib/dhall/ast.rb => lib/dhall/ast.rb +12 -0
@@ 612,6 612,18 @@ module Dhall
end
end
+ class ToMap < Expression
+ include(ValueSemantics.for_attributes do
+ record Expression
+ type Either(Expression, nil), default: nil
+ end)
+
+ def as_json
+ [27, record.as_json] +
+ (type.nil? ? [] : [type.as_json])
+ end
+ end
+
class RecordType < Expression
include(ValueSemantics.for_attributes do
record Util::HashOf.new(::String, Expression, min: 1)
M lib/dhall/binary.rb => lib/dhall/binary.rb +11 -1
@@ 113,6 113,15 @@ module Dhall
end
end
+ class ToMap
+ def self.decode(record, type=nil)
+ new(
+ record: Dhall.decode(record),
+ type: type.nil? ? nil : Dhall.decode(type)
+ )
+ end
+ end
+
class Merge
def self.decode(record, input, type=nil)
new(
@@ 335,6 344,7 @@ module Dhall
nil,
Import,
LetBlock,
- TypeAnnotation
+ TypeAnnotation,
+ ToMap
].freeze
end
M lib/dhall/normalize.rb => lib/dhall/normalize.rb +14 -0
@@ 265,6 265,20 @@ module Dhall
end
end
+ class ToMap
+ def normalize
+ normalized = super
+ unless [Record, EmptyRecord].include?(normalized.record.class)
+ return normalized
+ end
+
+ List.of(*normalized.record.to_h.to_a.map do |(k, v)|
+ k = Text.new(value: k)
+ Record.new(record: { "mapKey" => k, "mapValue" => v })
+ end, type: normalized.type&.argument)
+ end
+ end
+
class Merge
def normalize
normalized = super
M lib/dhall/parser.rb => lib/dhall/parser.rb +14 -24
@@ 21,8 21,10 @@ module Dhall
module Expression
def value
+ return list if string =~ /\A\[\s*\]/
+
key =
- [:let_binding, :lambda, :forall, :arrow, :if, :merge, :list]
+ [:let_binding, :lambda, :forall, :arrow, :if, :merge, :tomap]
.find { |k| captures.key?(k) }
key ? public_send(key) : super
@@ 75,7 77,14 @@ module Dhall
end
def list
- EmptyList.new(element_type: capture(:import_expression).value)
+ EmptyList.new(type: capture(:application_expression).value)
+ end
+
+ def tomap
+ ToMap.new(
+ record: capture(:import_expression).value,
+ type: capture(:application_expression).value
+ )
end
end
@@ 121,9 130,9 @@ module Dhall
if captures.key?(:merge)
merge
elsif captures.key?(:some)
- Optional.new(
- value: capture(:import_expression).value
- )
+ Optional.new(value: capture(:import_expression).value)
+ elsif captures.key?(:tomap)
+ ToMap.new(record: capture(:import_expression).value)
else
super
end
@@ 467,25 476,6 @@ module Dhall
RecordTypeEntry = RecordLiteralEntry
- module EmptyCollection
- def value
- if captures.key?(:list)
- EmptyList.new(element_type: capture(:import_expression).value)
- else
- OptionalNone.new(value_type: capture(:import_expression).value)
- end
- end
- end
-
- module NonEmptyOptional
- def value
- Optional.new(
- value: capture(:expression).value,
- value_type: capture(:import_expression).value
- )
- end
- end
-
module AnnotatedExpression
def value
if matches[1].string.empty?
M lib/dhall/typecheck.rb => lib/dhall/typecheck.rb +33 -0
@@ 679,6 679,39 @@ module Dhall
end
end
+ class ToMap
+ TypeChecker.register self, Dhall::ToMap
+
+ def initialize(tomap)
+ @tomap = tomap
+ @record = TypeChecker.for(tomap.record)
+ end
+
+ def check_annotation(record_type)
+ if record_type.is_a?(Dhall::EmptyRecordType)
+ TypeChecker.assert @tomap.type, Dhall::Expression,
+ "toMap {=} has no annotation"
+ else
+ t = Types::MAP(v: record_type.record.values.first)
+
+ TypeChecker.assert t, (@tomap.type || t),
+ "toMap does not match annotation"
+ end
+ end
+
+ def annotate(context)
+ record_type = @record.annotate(context).type
+ TypeChecker.assert record_type, Dhall::RecordType,
+ "toMap on a non-record: #{record_type.inspect}"
+
+ TypeChecker.assert record_type.record.values, Util::ArrayAllTheSame,
+ "toMap heterogenous: #{record_type.inspect}"
+
+ type = check_annotation(record_type)
+ Dhall::TypeAnnotation.new(value: @tomap, type: type)
+ end
+ end
+
class Merge
TypeChecker.register self, Dhall::Merge