# frozen_string_literal: true
require "ostruct"
require "psych"
module Dhall
module AsDhall
TAGS = {
::Integer => "Integer",
::FalseClass => "Bool",
::Integer => "Integer",
::Float => "Double",
::NilClass => "None",
::String => "Text",
::TrueClass => "Bool"
}.freeze
def self.tag_for(o, type)
return "Natural" if o.is_a?(::Integer) && !o.negative?
TAGS.fetch(o.class) do
"#{o.class.name}_#{type.digest.hexdigest}"
end
end
def self.union_of(values_and_types)
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]
]
end
end
refine ::String do
def as_dhall
if encoding == Encoding::BINARY
bytes.as_dhall
else
Text.new(value: self)
end
end
end
refine ::Symbol do
def as_dhall
Dhall::Enum.new(
tag: to_s,
alternatives: Dhall::UnionType.new(alternatives: {})
)
end
end
refine ::Integer do
def as_dhall
if negative?
Integer.new(value: self)
else
Natural.new(value: self)
end
end
end
refine ::Float do
def as_dhall
Double.new(value: self)
end
end
refine ::TrueClass do
def as_dhall
Bool.new(value: true)
end
end
refine ::FalseClass do
def as_dhall
Bool.new(value: false)
end
end
refine ::NilClass do
def as_dhall
raise(
"Cannot call NilClass#as_dhall directly, " \
"you probably want to create a Dhall::OptionalNone yourself."
)
end
end
module ExpressionList
def self.for(values, exprs)
types = exprs.map(&TypeChecker.method(:type_of))
if types.empty?
Empty
elsif types.include?(nil) && types.uniq.length <= 2
Optional
elsif types.uniq.length == 1
Mono
else
Union
end.new(values, exprs, types)
end
class Empty
def initialize(*); end
def list
EmptyList.new(element_type: UnionType.new(alternatives: {}))
end
end
class Optional
def initialize(_, exprs, types)
@type = types.compact.first
@exprs = exprs
end
def list
List.new(elements: @exprs.map do |x|
if x.nil?
Dhall::OptionalNone.new(value_type: @type)
else
Dhall::Optional.new(value: x)
end
end)
end
end
class Mono
def initialize(_, exprs, _)
@exprs = exprs
end
def list
List.new(elements: @exprs)
end
end
class Union
def initialize(values, exprs, types)
@values = values
@exprs = exprs
@types = types
end
def list
ut, tags = AsDhall.union_of(@values.zip(@types))
List.new(elements: @exprs.zip(tags).map do |(expr, tag)|
if expr.is_a?(Dhall::Union) && expr.alternatives.empty?
expr = expr.is_a?(Dhall::Enum) ? nil : expr.extract
end
Dhall::Union.from(ut, tag, expr)
end)
end
end
end
refine ::Array do
def as_dhall
ExpressionList.for(self, map { |x| x&.as_dhall }).list
end
end
refine ::Hash do
def as_dhall
if empty?
EmptyRecord.new
else
Record.new(record: Hash[
reject { |_, v| v.nil? }
.map { |k, v| [k.to_s, v.as_dhall] }
.sort
])
end
end
end
refine ::OpenStruct do
def as_dhall
expr = to_h.as_dhall
type = TypeChecker.for(expr).annotate(TypeChecker::Context.new).type
Union.from(
UnionType.new(alternatives: { "OpenStruct" => type }),
"OpenStruct",
expr
)
end
end
refine ::Psych::Coder do
def as_dhall
case type
when :seq
seq
when :map
map
else
scalar
end.as_dhall
end
end
refine ::Object do
def as_dhall
tag = self.class.name
expr = Util.psych_coder_from(tag, self).as_dhall
type = TypeChecker.for(expr).annotate(TypeChecker::Context.new).type
Union.from(
UnionType.new(alternatives: { tag => type }),
tag,
expr
)
end
end
refine ::Proc do
def as_dhall
FunctionProxy.new(self)
end
end
end
end