~singpolyma/dhall-ruby

b958e41b20a238798e611c516bc79c9f55ac93c1 — Stephen Paul Weber 4 years ago 4523d4b
Add grab-bag of useful utils onto expressions
5 files changed, 213 insertions(+), 20 deletions(-)

M .rubocop.yml
M lib/dhall/as_dhall.rb
M lib/dhall/ast.rb
M lib/dhall/normalize.rb
M lib/dhall/resolve.rb
M .rubocop.yml => .rubocop.yml +3 -0
@@ 65,6 65,9 @@ Style/FormatString:
Style/RegexpLiteral:
  AllowInnerSlashes: true

Style/SpecialGlobalVars:
  EnforcedStyle: use_perl_names

Style/StringLiterals:
  EnforcedStyle: double_quotes


M lib/dhall/as_dhall.rb => lib/dhall/as_dhall.rb +0 -4
@@ 2,10 2,6 @@

require "ostruct"

require "dhall/ast"
require "dhall/binary"
require "dhall/typecheck"

module Dhall
	module AsDhall
		TAGS = {

M lib/dhall/ast.rb => lib/dhall/ast.rb +165 -9
@@ 3,9 3,12 @@
require "uri"
require "value_semantics"

require "dhall/as_dhall"
require "dhall/util"

module Dhall
	using AsDhall

	class Expression
		def call(*args)
			args.reduce(self) { |f, arg|


@@ 161,6 164,7 @@ module Dhall
		end

		def call(*args)
			args.map! { |arg| arg&.as_dhall }
			return super if args.length > 1

			body.substitute(


@@ 169,6 173,8 @@ module Dhall
			).shift(-1, var, 0).normalize
		end

		alias [] call

		def as_json
			if var == "_"
				[1, type.as_json, body.as_json]


@@ 213,6 219,18 @@ module Dhall
			end
		end

		def !@
			with(value: !value)
		end

		def ===(other)
			self == other || value === other
		end

		def to_s
			reduce("True", "False")
		end

		def as_json
			value
		end


@@ 311,14 329,18 @@ module Dhall
			self
		end

		def reduce(z)
			elements.reverse.reduce(z) { |acc, x| yield x, acc }
		def reduce(*z)
			elements.reverse.reduce(*z) { |acc, x| yield x, acc }
		end

		def length
			elements.length
		end

		def [](idx)
			Optional.for(elements[idx.to_i], type: element_type)
		end

		def first
			Optional.for(elements.first, type: element_type)
		end


@@ 331,6 353,10 @@ module Dhall
			with(elements: elements.reverse)
		end

		def join(sep=$,)
			elements.map(&:to_s).join(sep)
		end

		def concat(other)
			if other.is_a?(List) && !other.is_a?(EmptyList)
				with(elements: elements + other.elements)


@@ 365,6 391,10 @@ module Dhall
			0
		end

		def [](_)
			OptionalNone.new(value_type: element_type)
		end

		def first
			OptionalNone.new(value_type: element_type)
		end


@@ 396,6 426,11 @@ module Dhall
			end
		end

		def initialize(normalized: false, **attrs)
			@normalized = normalized
			super(**attrs)
		end

		def type
			return unless value_type



@@ 413,8 448,12 @@ module Dhall
			block[value]
		end

		def to_s
			value.to_s
		end

		def as_json
			[5, value_type&.as_json, value.as_json]
			[5, @normalized ? nil : value_type&.as_json, value.as_json]
		end
	end



@@ 431,6 470,10 @@ module Dhall
			z
		end

		def to_s
			""
		end

		def as_json
			[0, Variable["None"].as_json, value_type.as_json]
		end


@@ 522,27 565,68 @@ module Dhall
	end

	class Record < Expression
		include Enumerable

		include(ValueSemantics.for_attributes do
			record Util::HashOf.new(::String, Expression, min: 1)
		end)

		def self.for(record)
			if record.empty?
				EmptyRecord.new
			else
				new(record: record)
			end
		end

		def each(&block)
			record.each(&block)
			self
		end

		def to_h
			record
		end

		def keys
			record.keys
		end

		def values
			record.values
		end

		def [](k)
			record[k.to_s]
		end

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

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

		def dig(*keys)
			if keys.empty?
				raise ArgumentError, "wrong number of arguments (given 0, expected 1+)"
			end

			key, *rest = keys.map(&:to_s)
			v = record.fetch(key) { return nil }
			return v if rest.empty?

			v.dig(*rest)
		end

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

			with(record: Hash[record.merge(other.record) { |_, v1, v2|


@@ 551,6 635,7 @@ module Dhall
		end

		def merge(other)
			other = other.as_dhall
			return super unless other.is_a?(Record)

			with(record: Hash[record.merge(other.record).sort])


@@ 568,14 653,28 @@ module Dhall
			self == other
		end

		def with(attrs)
			self.class.new({ record: record }.merge(attrs))
		end

		def as_json
			[8, Hash[record.to_a.map { |k, v| [k, v.as_json] }.sort]]
		end
	end

	class EmptyRecord < Expression
		include Enumerable

		include(ValueSemantics.for_attributes {})

		def each
			self
		end

		def to_h
			{}
		end

		def keys
			[]
		end


@@ 681,9 780,10 @@ module Dhall
		end

		def get_constructor(selector)
			var = Util::BuiltinName === selector ? "_" : selector
			type = alternatives.fetch(selector)
			body = Union.from(self, selector, Variable[selector])
			Function.new(var: selector, type: type, body: body)
			body = Union.from(self, selector, Variable[var])
			Function.new(var: var, type: type, body: body)
		end

		def constructor_types


@@ 691,7 791,8 @@ module Dhall
				ctypes[k] = if type.nil?
					self
				else
					Forall.new(var: k, type: type, body: self)
					var = Util::BuiltinName === k ? "_" : k
					Forall.new(var: var, type: type, body: self)
				end
			end
		end


@@ 721,6 822,31 @@ module Dhall
			)
		end

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

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

		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
		end

		def selection_syntax
			RecordSelection.new(
				record:   alternatives.merge(


@@ 767,7 893,12 @@ module Dhall
			value (0..Float::INFINITY)
		end)

		def coerce(other)
			[other.as_dhall, self]
		end

		def +(other)
			other = other.as_dhall
			if other.is_a?(Natural)
				with(value: value + other.value)
			else


@@ 776,6 907,7 @@ module Dhall
		end

		def *(other)
			other = other.as_dhall
			return self if zero?
			if other.is_a?(Natural)
				with(value: value * other.value)


@@ 784,6 916,10 @@ module Dhall
			end
		end

		def to_i
			value
		end

		def to_s
			value.to_s
		end


@@ 804,6 940,10 @@ module Dhall
			with(value: [0, value - 1].max)
		end

		def ===(other)
			self == other || value === other
		end

		def as_json
			[15, value]
		end


@@ 818,6 958,14 @@ module Dhall
			"#{value >= 0 ? "+" : ""}#{value}"
		end

		def to_i
			value
		end

		def ===(other)
			self == other || value === other
		end

		def as_json
			[16, value]
		end


@@ 836,6 984,10 @@ module Dhall
			value
		end

		def ===(other)
			self == other || value === other
		end

		def coerce(other)
			return [other, self] if other.is_a?(Double)
			[Double.new(value: other.to_f), self]


@@ 890,6 1042,10 @@ module Dhall
			value
		end

		def ===(other)
			self == other || value === other
		end

		def as_json
			[18, value]
		end

M lib/dhall/normalize.rb => lib/dhall/normalize.rb +35 -7
@@ 241,12 241,6 @@ module Dhall
		end
	end

	class List
		def normalize
			super.with(element_type: nil)
		end
	end

	class EmptyList
		def normalize
			super.with(element_type: element_type.normalize)


@@ 255,7 249,11 @@ module Dhall

	class Optional
		def normalize
			with(value: value.normalize, value_type: nil)
			with(
				value:      value.normalize,
				value_type: value_type&.normalize,
				normalized: true
			)
		end
	end



@@ 278,6 276,36 @@ module Dhall
		end
	end

	class Record
		def normalize
			with(record: Hash[
				record.map { |k, v| [k, v.nil? ? v : v.normalize] }.sort
			])
		end

		def shift(amount, name, min_index)
			with(record: Hash[
				record.map { |k, v|
					[k, v.nil? ? v : v.shift(amount, name, min_index)]
				}.sort
			])
		end

		def substitute(var, with_expr)
			with(record: Hash[
				record.map { |k, v|
					[k, v.nil? ? v : v.substitute(var, with_expr)]
				}.sort
			])
		end
	end

	class EmptyRecord
		def normalize
			self
		end
	end

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

M lib/dhall/resolve.rb => lib/dhall/resolve.rb +10 -0
@@ 346,6 346,16 @@ module Dhall
			end
		end

		class RecordResolver < ExpressionResolver
			register_for Record

			def resolve(**kwargs)
				ExpressionResolver.for(@expr.record).resolve(**kwargs).then do |h|
					@expr.with(record: h)
				end
			end
		end

		register_for Expression

		class IdentityResolver < ExpressionResolver