~singpolyma/dhall-ruby

831a391f421d60eed8933d38ed3cc7149d018900 — Stephen Paul Weber 3 years ago afdc558
Generic subexpression_map

Make every Expression a ValueSemantics and use the known list of
attributes to map generically over any AST node.

Introduces a conditional, but removes boilerplate from every single
class.
M .rubocop.yml => .rubocop.yml +3 -0
@@ 35,6 35,9 @@ Style/BlockDelimiters:
Style/MultilineBlockChain:
  Enabled: false

Style/CaseEquality:
  Enabled: false

Style/ClassVars:
  Enabled: false


M lib/dhall/ast.rb => lib/dhall/ast.rb +39 -147
@@ 1,14 1,14 @@
# frozen_string_literal: true

require "uri"
require "value_semantics"

require "dhall/util"

module Dhall
	class Expression
		def map_subexpressions(&_)
			# For expressions with no subexpressions
			self
		def map_subexpressions(&block)
			with(subexpression_map(&block))
		end

		def call(*args)


@@ 47,12 47,7 @@ module Dhall
		end

		def concat(other)
			case other
			when EmptyList
				self
			else
				Operator::ListConcatenate.new(lhs: self, rhs: other)
			end
			Operator::ListConcatenate.new(lhs: self, rhs: other)
		end

		def &(other)


@@ 111,6 106,21 @@ module Dhall
				Operator::RecursiveRecordTypeMerge.new(lhs: self, rhs: other)
			end
		end

		protected

		def subexpression_map(&block)
			to_h.each_with_object({}) do |(attr, value), h|
				case value
				when Expression
					h[attr] = block[value]
				when Util::ArrayOf.new(Expression)
					h[attr] = value.map(&block)
				when Util::HashOf.new(Expression)
					h[attr] = Hash[value.map { |k, v| [k, block[v]] }]
				end
			end
		end
	end

	class Application < Expression


@@ 118,10 128,6 @@ module Dhall
			function Expression
			arguments Util::ArrayOf.new(Expression, min: 1)
		end)

		def map_subexpressions(&block)
			with(function: block[function], arguments: arguments.map(&block))
		end
	end

	class Function < Expression


@@ 141,10 147,6 @@ module Dhall
			end
		end

		def map_subexpressions(&block)
			with(var: var, type: type.nil? ? nil : block[type], body: block[body])
		end

		def call(*args)
			return super if args.length > 1



@@ 196,10 198,6 @@ module Dhall
			rhs Expression
		end)

		def map_subexpressions(&block)
			with(lhs: block[@lhs], rhs: block[@rhs])
		end

		class Or < Operator; end
		class And < Operator; end
		class Equal < Operator; end


@@ 223,10 221,6 @@ module Dhall
			List.new(elements: args)
		end

		def map_subexpressions(&block)
			with(elements: elements.map(&block))
		end

		def type
			# TODO: inferred element type
		end


@@ 269,10 263,6 @@ module Dhall
			type Expression
		end)

		def map_subexpressions(&block)
			with(type: block[@type])
		end

		def map(type: nil)
			type.nil? ? self : with(type: type)
		end


@@ 308,10 298,6 @@ module Dhall
			type Either(nil, Expression), default: nil
		end)

		def map_subexpressions(&block)
			with(value: block[value], type: type.nil? ? type : block[type])
		end

		def reduce(_, &block)
			block[value]
		end


@@ 322,10 308,6 @@ module Dhall
			type Expression
		end)

		def map_subexpressions(&block)
			with(type: block[@type])
		end

		def reduce(z)
			z
		end


@@ 337,33 319,18 @@ module Dhall
			input  Expression
			type   Either(Expression, nil)
		end)

		def map_subexpressions(&block)
			with(
				record: block[record],
				input:  block[input],
				type:   type.nil? ? nil : block[type]
			)
		end
	end

	class RecordType < Expression
		attr_reader :record

		def initialize(record)
			raise ArgumentError, "You meant EmptyRecordType?" if record.empty?

			@record = record
		end

		def map_subexpressions(&block)
			self.class.new(Hash[@record.map { |k, v| [k, block[v]] }])
		end
		include(ValueSemantics.for_attributes do
			# The nil is not allowed in Dhall
			record Util::HashOf.new(Either(nil, Expression), min: 1)
		end)

		def deep_merge_type(other)
			return super unless other.is_a?(RecordType)

			self.class.new(Hash[record.merge(other.record) { |_, v1, v2|
			with(record: Hash[record.merge(other.record) { |_, v1, v2|
				v1.deep_merge_type(v2)
			}.sort])
		end


@@ 380,27 347,15 @@ module Dhall
	class EmptyRecordType < Expression
		include(ValueSemantics.for_attributes {})

		def map_subexpressions
			self
		end

		def deep_merge_type(other)
			other
		end
	end

	class Record < Expression
		attr_reader :record

		def initialize(record)
			raise ArgumentError, "You meant EmptyRecord?" if record.empty?

			@record = record
		end

		def map_subexpressions(&block)
			self.class.new(Hash[@record.map { |k, v| [k, block[v]] }])
		end
		include(ValueSemantics.for_attributes do
			record Util::HashOf.new(Expression, min: 1)
		end)

		def fetch(k, default=nil, &block)
			record.fetch(k, *default, &block)


@@ 408,16 363,16 @@ module Dhall

		def slice(*keys)
			if record.respond_to?(:slice)
				self.class.new(record.slice(*keys))
				with(record: record.slice(*keys))
			else
				self.class.new(record.select { |k, _| keys.include?(k) })
				with(record: record.select { |k, _| keys.include?(k) })
			end
		end

		def deep_merge(other)
			return super unless other.is_a?(Record)

			self.class.new(Hash[record.merge(other.record) { |_, v1, v2|
			with(record: Hash[record.merge(other.record) { |_, v1, v2|
				v1.deep_merge(v2)
			}.sort])
		end


@@ 425,7 380,7 @@ module Dhall
		def merge(other)
			return super unless other.is_a?(Record)

			self.class.new(Hash[record.merge(other.record).sort])
			with(record: Hash[record.merge(other.record).sort])
		end

		def ==(other)


@@ 440,10 395,6 @@ module Dhall
	class EmptyRecord < Expression
		include(ValueSemantics.for_attributes {})

		def map_subexpressions
			self
		end

		def fetch(k, default=nil, &block)
			{}.fetch(k, *default, &block)
		end


@@ 466,10 417,6 @@ module Dhall
			record Expression
			selector ::String
		end)

		def map_subexpressions(&block)
			with(record: block[record], selector: selector)
		end
	end

	class RecordProjection < Expression


@@ 477,35 424,21 @@ module Dhall
			record Expression
			selectors Util::ArrayOf.new(::String, min: 1)
		end)

		def map_subexpressions(&block)
			with(record: block[record], selectors: selectors)
		end
	end

	class EmptyRecordProjection < Expression
		include(ValueSemantics.for_attributes do
			record Expression
		end)

		def map_subexpressions(&block)
			with(record: block[record])
		end
	end

	class UnionType < Expression
		attr_reader :record

		def initialize(record)
			@record = record
		end

		def map_subexpressions(&block)
			self.class.new(Hash[@record.map { |k, v| [k, block[v]] }])
		end
		include(ValueSemantics.for_attributes do
			alternatives Util::HashOf.new(Expression)
		end)

		def ==(other)
			other.respond_to?(:record) && record.to_a == other.record.to_a
			other.is_a?(UnionType) && alternatives.to_a == other.alternatives.to_a
		end

		def eql?(other)


@@ 513,13 446,14 @@ module Dhall
		end

		def fetch(k)
			remains = with(alternatives: alternatives.dup.tap { |r| r.delete(k) })
			Function.new(
				var:  k,
				type: record.fetch(k),
				type: alternatives.fetch(k),
				body: Union.new(
					tag:          k,
					value:        Variable.new(name: k),
					alternatives: self.class.new(record.dup.tap { |r| r.delete(k) })
					alternatives: remains
				)
			)
		end


@@ 531,14 465,6 @@ module Dhall
			value        Expression
			alternatives UnionType
		end)

		def map_subexpressions(&block)
			with(
				tag:          tag,
				value:        block[value],
				alternatives: block[alternatives]
			)
		end
	end

	class If < Expression


@@ 547,14 473,6 @@ module Dhall
			self.then Expression
			self.else Expression
		end)

		def map_subexpressions(&block)
			with(
				predicate: block[predicate],
				then:      block[self.then],
				else:      block[self.else]
			)
		end
	end

	class Number < Expression


@@ 641,10 559,6 @@ module Dhall
		include(ValueSemantics.for_attributes do
			chunks ArrayOf(Expression)
		end)

		def map_subexpressions(&block)
			with(chunks: chunks.map(&block))
		end
	end

	class Import < Expression


@@ 694,20 608,12 @@ module Dhall
		end
	end

	class Let
	class Let < Expression
		include(ValueSemantics.for_attributes do
			var    ::String
			assign Expression
			type   Either(nil, Expression)
		end)

		def map_subexpressions(&block)
			with(
				var:    var,
				assign: block[assign],
				type:   type.nil? ? nil : block[type]
			)
		end
	end

	class LetBlock < Expression


@@ 715,13 621,6 @@ module Dhall
			lets ArrayOf(Let)
			body Expression
		end)

		def map_subexpressions(&block)
			with(
				body: block[body],
				lets: lets.map { |let| let.map_subexpressions(&block) }
			)
		end
	end

	class TypeAnnotation < Expression


@@ 729,12 628,5 @@ module Dhall
			value Expression
			type  Expression
		end)

		def map_subexpressions(&block)
			with(
				value: block[value],
				type:  block[type]
			)
		end
	end
end

M lib/dhall/binary.rb => lib/dhall/binary.rb +3 -3
@@ 105,7 105,7 @@ module Dhall
			if record.empty?
				EmptyRecordType.new
			else
				new(Hash[record.map { |k, v| [k, Dhall.decode(v)] }])
				new(record: Hash[record.map { |k, v| [k, Dhall.decode(v)] }])
			end
		end
	end


@@ 115,7 115,7 @@ module Dhall
			if record.empty?
				EmptyRecord.new
			else
				new(Hash[record.map { |k, v| [k, Dhall.decode(v)] }])
				new(record: Hash[record.map { |k, v| [k, Dhall.decode(v)] }])
			end
		end
	end


@@ 138,7 138,7 @@ module Dhall

	class UnionType
		def self.decode(record)
			new(Hash[record.map { |k, v| [k, Dhall.decode(v)] }])
			new(alternatives: Hash[record.map { |k, v| [k, Dhall.decode(v)] }])
		end
	end


M lib/dhall/builtins.rb => lib/dhall/builtins.rb +24 -9
@@ 4,9 4,7 @@ require "dhall/ast"

module Dhall
	class Builtin < Expression
		def ==(other)
			self.class == other.class
		end
		include(ValueSemantics.for_attributes {})

		def call(*args)
			# Do not auto-normalize builtins to avoid recursion loop


@@ 234,16 232,33 @@ module Dhall
		class List_indexed < Builtin
			def call(arg)
				if arg.is_a?(List)
					arg.map(type: RecordType.new(
						"index" => Variable.new(name: "Natural"),
						"value" => arg.type
					)) do |x, idx|
						Record.new("index" => Natural.new(value: idx), "value" => x)
					end
					_call(arg)
				else
					super
				end
			end

			protected

			def _call(arg)
				arg.map(type: indexed_type(arg.type)) do |x, idx|
					Record.new(
						record: {
							"index" => Natural.new(value: idx),
							"value" => x
						}
					)
				end
			end

			def indexed_type(value_type)
				RecordType.new(
					record: {
						"index" => Variable.new(name: "Natural"),
						"value" => value_type
					}
				)
			end
		end

		class List_last < Builtin

M lib/dhall/normalize.rb => lib/dhall/normalize.rb +8 -20
@@ 184,7 184,13 @@ module Dhall

		class ListConcatenate
			def normalize
				lhs.normalize.concat(rhs.normalize)
				normalized = super
				case normalized.rhs
				when EmptyList
					normalized.lhs
				else
					normalized.lhs.concat(normalized.rhs)
				end
			end
		end



@@ 238,24 244,6 @@ module Dhall
		end
	end

	class RecordType
		def normalize
			self.class.new(Hash[record.sort.map { |(k, v)| [k, v.normalize] }])
		end
	end

	class Record
		def normalize
			self.class.new(Hash[record.sort.map { |(k, v)| [k, v.normalize] }])
		end
	end

	class EmptyRecord
		def normalize
			self
		end
	end

	class RecordSelection
		def normalize
			record.normalize.fetch(selector)


@@ 276,7 264,7 @@ module Dhall

	class UnionType
		def normalize
			self.class.new(Hash[super.record.sort])
			with(alternatives: Hash[super.alternatives.sort])
		end
	end


M lib/dhall/util.rb => lib/dhall/util.rb +14 -0
@@ 13,5 13,19 @@ module Dhall
				super && other.length >= @min && other.length <= @max
			end
		end

		class HashOf
			def initialize(element_validator, min: 0, max: Float::INFINITY)
				@min = min
				@max = max
				@element_validator = element_validator
			end

			def ===(other)
				Hash === other &&
					other.values.all? { |x| @element_validator === x } &&
					other.size >= @min && other.size <= @max
			end
		end
	end
end