~singpolyma/dhall-ruby

6e28edfe220d024ceed9cc01bc43b1aede670681 — Stephen Paul Weber 2 years ago a49a899
Split Union and Enum

Stop putting special case conditionals for Union#value.nil? everywhere
and just split the classes.
M lib/dhall/as_dhall.rb => lib/dhall/as_dhall.rb +5 -4
@@ 24,9 24,11 @@ module Dhall
		end

		def self.union_of(values_and_types)
			z = [UnionType.new(alternatives: {}), []]
			values_and_types.reduce(z) do |(ut, tags), (v, t)|
			values_and_types.reduce([UnionType.new, []]) do |(ut, tags), (v, t)|
				tag = tag_for(v, t)
				if t.is_a?(UnionType) && t.alternatives.length == 1
					tag, t = t.alternatives.to_a.first
				end
				[
					ut.merge(UnionType.new(alternatives: { tag => t })),
					tags + [tag]


@@ 46,9 48,8 @@ module Dhall

		refine ::Symbol do
			def as_dhall
				Dhall::Union.new(
				Dhall::Enum.new(
					tag:          to_s,
					value:        nil,
					alternatives: Dhall::UnionType.new(alternatives: {})
				)
			end

M lib/dhall/ast.rb => lib/dhall/ast.rb +57 -30
@@ 764,9 764,22 @@ module Dhall

	class UnionType < Expression
		include(ValueSemantics.for_attributes do
			alternatives Util::HashOf.new(::String, Either(Expression, nil))
			alternatives Util::HashOf.new(::String, Either(Expression, nil)), default: {}
		end)

		def empty?
			alternatives.empty?
		end

		def [](k)
			alternatives.fetch(k)
		end

		def without(key)
			key = key.to_s
			with(alternatives: alternatives.reject { |k, _| k == key })
		end

		def record
			alternatives
		end


@@ 817,31 830,28 @@ module Dhall
	class Union < Expression
		include(ValueSemantics.for_attributes do
			tag          ::String
			value        Either(Expression, nil)
			value        Expression
			alternatives UnionType
		end)

		def self.from(alts, tag, value)
			new(
				tag:          tag,
				value:        value && TypeAnnotation.new(
					value: value,
					type:  alts.alternatives[tag]
				),
				alternatives: alts.with(
					alternatives: alts.alternatives.reject { |alt, _| alt == tag }
			if value.nil?
				Enum.new(tag: tag, alternatives: alts)
			else
				new(
					tag:          tag,
					value:        TypeAnnotation.new(value: value, type: alts[tag]),
					alternatives: alts.without(tag)
				)
			)
			end
		end

		def to_s
			value.nil? ? tag : extract.to_s
			extract.to_s
		end

		def extract
			if value.nil?
				tag.to_sym
			elsif value.is_a?(TypeAnnotation)
			if value.is_a?(TypeAnnotation)
				value.value
			else
				value


@@ 851,12 861,8 @@ module Dhall
		def reduce(handlers)
			handlers = handlers.to_h
			handler = handlers.fetch(tag.to_sym) { handlers.fetch(tag) }
			if value.nil?
				handler
			else
				(handler.respond_to?(:to_proc) ? handler.to_proc : handler)
					.call(extract)
			end
			(handler.respond_to?(:to_proc) ? handler.to_proc : handler)
				.call(extract)
		end

		def selection_syntax


@@ 869,18 875,14 @@ module Dhall
		end

		def syntax
			if value.nil?
				selection_syntax
			else
				Application.new(
					function: selection_syntax,
					argument: value.is_a?(TypeAnnotation) ? value.value : value
				)
			end
			Application.new(
				function: selection_syntax,
				argument: value.is_a?(TypeAnnotation) ? value.value : value
			)
		end

		def as_json
			if value.nil? || value.respond_to?(:type)
			if value.respond_to?(:type)
				syntax.as_json
			else
				[12, tag, value&.as_json, alternatives.as_json.last]


@@ 888,6 890,31 @@ module Dhall
		end
	end

	class Enum < Union
		include(ValueSemantics.for_attributes do
			tag          ::String
			alternatives UnionType
		end)

		def reduce(handlers)
			handlers = handlers.to_h
			handler = handlers.fetch(tag.to_sym) { handlers.fetch(tag) }
			handler
		end

		def to_s
			tag
		end

		def extract
			tag.to_sym
		end

		def as_json
			selection_syntax.as_json
		end
	end

	class If < Expression
		include(ValueSemantics.for_attributes do
			predicate Expression

M lib/dhall/typecheck.rb => lib/dhall/typecheck.rb +19 -0
@@ 604,6 604,25 @@ module Dhall
			end
		end

		class Enum
			TypeChecker.register self, Dhall::Enum

			def initialize(enum)
				@enum = enum
			end

			def annotate(context)
				type = Dhall::UnionType.new(
					alternatives: { @enum.tag => nil }
				).merge(@enum.alternatives)

				# Annotate to sanity check
				TypeChecker.for(type).annotate(context)

				Dhall::TypeAnnotation.new(value: @enum, type: type)
			end
		end

		class Union
			TypeChecker.register self, Dhall::Union


M test/test_as_dhall.rb => test/test_as_dhall.rb +1 -2
@@ 27,9 27,8 @@ class TestAsDhall < Minitest::Test

	def test_symbol
		assert_equal(
			Dhall::Union.new(
			Dhall::Enum.new(
				tag:          "hai",
				value:        nil,
				alternatives: Dhall::UnionType.new(alternatives: {})
			),
			:hai.as_dhall

M test/test_typechecker.rb => test/test_typechecker.rb +14 -0
@@ 112,4 112,18 @@ class TestTypechecker < Minitest::Test
			)
		end
	end

	def test_enum
		union = Dhall::Enum.new(
			tag:          "red",
			alternatives: Dhall::UnionType.new(alternatives: {})
		)

		assert_equal(
			Dhall::UnionType.new(alternatives: { "red" => nil }),
			Dhall::TypeChecker.for(union).annotate(
				Dhall::TypeChecker::Context.new
			).type
		)
	end
end