~singpolyma/dhall-ruby

68fb06eb18a0486003cf1e9dffec35d18e6b9c95 — Stephen Paul Weber 3 years ago 2b29af3
Implement toMap
5 files changed, 84 insertions(+), 25 deletions(-)

M lib/dhall/ast.rb
M lib/dhall/binary.rb
M lib/dhall/normalize.rb
M lib/dhall/parser.rb
M lib/dhall/typecheck.rb
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