# frozen_string_literal: true require "cbor" module Dhall def self.from_binary(cbor_binary) data = CBOR.decode(cbor_binary) if data.is_a?(Array) && data[0] == "5.0.0" decode(data[1]) else decode(data) end end def self.decode(expression) return expression if expression.is_a?(Expression) BINARY.each do |match, use| return use[expression] if expression.is_a?(match) end raise "Unknown expression: #{expression.inspect}" end class Expression def self.decode(*args) return new(value: args.first) if args.length == 1 new(*args) end end class Application def self.decode(f, *args) new(Dhall.decode(f), *args.map(&Dhall.method(:decode))) end end class Function def self.decode(var_or_type, type_or_body, body_or_nil=nil) if body_or_nil.nil? new("_", Dhall.decode(var_or_type), Dhall.decode(type_or_body)) else unless var_or_type.is_a?(String) raise TypeError, "Function var must be a String" end raise ArgumentError, "explicit var named _" if var_or_type == "_" new(var_or_type, Dhall.decode(type_or_body), Dhall.decode(body_or_nil)) end end end class Operator OPERATORS = [ Or, And, Equal, NotEqual, Plus, Times, TextConcatenate, ListConcatenate, RecordMerge, RecordOverride, RecordTypeMerge, ImportFallback ].freeze def self.decode(opcode, lhs, rhs) OPERATORS[opcode].new( lhs: Dhall.decode(lhs), rhs: Dhall.decode(rhs) ) end end class List def self.decode(type, *els) if type.nil? List.new(elements: els.map(&Dhall.method(:decode))) else EmptyList.new(type: Dhall.decode(type)) end end end class Optional def self.decode(type, value=nil) if value.nil? OptionalNone.new(Dhall.decode(type)) else Optional.new( Dhall.decode(value), type.nil? ? type : Dhall.decode(type) ) end end end class Merge def self.decode(record, input, type=nil) new( Dhall.decode(record), Dhall.decode(input), type.nil? ? nil : Dhall.decode(type) ) end end class RecordType def self.decode(record) new(Hash[record.map { |k, v| [k, Dhall.decode(v)] }]) end end class Record def self.decode(record) new(Hash[record.map { |k, v| [k, Dhall.decode(v)] }]) end end class RecordFieldAccess def self.decode(record, field) new(Dhall.decode(record), field) end end class RecordProjection def self.decode(record, *fields) new(Dhall.decode(record), *fields) end end class UnionType def self.decode(record) new(Hash[record.map { |k, v| [k, Dhall.decode(v)] }]) end end class Union def self.decode(tag, value, rest_of_type) new( tag, Dhall.decode(value), Hash[rest_of_type.map { |k, v| [k, Dhall.decode(v)] }] ) end end class If def self.decode(pred, thn, els) new( predicate: Dhall.decode(pred), then: Dhall.decode(thn), else: Dhall.decode(els) ) end end class TextLiteral def self.decode(*chunks) lead_text, *pairs = chunks chunks = [Text.new(value: lead_text)] + pairs.each_slice(2).flat_map do |(e, t)| [Dhall.decode(e), Text.new(value: t)] end chunks.length == 1 ? chunks.first : TextLiteral.new(chunks: chunks) end end class Import IMPORT_TYPES = [Expression, Text].freeze PATH_TYPES = [ Http, Https, AbsolutePath, RelativePath, RelativeToParentPath, RelativeToHomePath, EnvironmentVariable, MissingImport ].freeze def self.decode(integrity_check, import_type, path_type, *parts) parts[0] = Dhall.decode(parts[0]) if path_type new( integrity_check.nil? ? nil : IntegrityCheck.new(*integrity_check), IMPORT_TYPES[import_type], PATH_TYPES[path_type].new(*parts) ) end end class LetBlock def self.decode(*parts) new( Dhall.decode(parts.pop), *parts.each_slice(3).map do |(var, type, assign)| Let.new( var, Dhall.decode(assign), type.nil? ? nil : Dhall.decode(type) ) end ) end end class TypeAnnotation def self.decode(value, type) new(Dhall.decode(value), Dhall.decode(type)) end end BINARY = { ::TrueClass => ->(e) { Bool.new(value: e) }, ::FalseClass => ->(e) { Bool.new(value: e) }, ::Float => ->(e) { Double.new(value: e) }, ::String => ->(e) { Variable.new(name: e) }, ::Integer => ->(e) { Variable.new(index: e) }, ::Array => lambda { |e| if e.length == 2 && e.first.is_a?(::String) Variable.new(name: e[0], index: e[1]) else tag, *body = e BINARY_TAGS[tag]&.decode(*body) || (raise "Unknown expression: #{e.inspect}") end } }.freeze BINARY_TAGS = [ Application, Function, Forall, Operator, List, Optional, Merge, RecordType, Record, RecordFieldAccess, RecordProjection, UnionType, Union, Constructors, If, Natural, Integer, nil, TextLiteral, nil, nil, nil, nil, nil, Import, LetBlock, TypeAnnotation ].freeze end