# frozen_string_literal: true require "dhall/ast" module Dhall class Builtin < Expression include(ValueSemantics.for_attributes {}) def call(*args) # Do not auto-normalize builtins to avoid recursion loop args.reduce(self) do |f, arg| Application.new(function: f, argument: arg) end end def unfill attributes.reduce(self.class.new) do |f, attr| if send(attr.name).nil? f else Application.new(function: f, argument: send(attr.name)) end end end def as_json if (unfilled = unfill).class != self.class unfilled.as_json else self.class.name&.split(/::/)&.last&.tr("_", "/").to_s end end protected def attributes self.class.value_semantics.attributes end def fill_next_if_valid(value) with(attributes.each_with_object({}) do |attr, h| if !send(attr.name).nil? h[attr.name] = send(attr.name) elsif attr.validate?(value) h[attr.name] = value value = nil else return nil end end) end def full? attributes.all? { |attr| !send(attr.name).nil? } end def fill_or_call(arg, &block) full? ? block[arg] : fill_next_if_valid(arg) end end module Builtins # rubocop:disable Style/ClassAndModuleCamelCase class Double_show < Builtin def call(arg) if arg.is_a?(Dhall::Double) Dhall::Text.new(value: arg.to_s) else super end end end class Integer_show < Builtin def call(arg) if arg.is_a?(Dhall::Integer) Dhall::Text.new(value: arg.to_s) else super end end end class Integer_toDouble < Builtin def call(arg) if arg.is_a?(Dhall::Integer) Dhall::Double.new(value: arg.value.to_f) else super end end end class Natural_build < Builtin def fusion(arg, *bogus) if bogus.empty? && arg.is_a?(Application) && arg.function == Natural_fold.new arg.argument else super end end def call(arg) arg.call( Natural.new, Function.of_arguments( Natural.new, body: Variable["_"] + Dhall::Natural.new(value: 1) ), Dhall::Natural.new(value: 0) ) end end class Natural_even < Builtin def call(nat) if nat.is_a?(Dhall::Natural) Dhall::Bool.new(value: nat.even?) else super end end end class Natural_fold < Builtin include(ValueSemantics.for_attributes do nat Either(nil, Dhall::Natural), default: nil type Either(nil, Expression), default: nil f Either(nil, Expression), default: nil end) def call(arg) fill_or_call(arg) do if @nat.zero? arg.normalize else @f.call(with(nat: nat.pred).call(arg)) end end || super end end class Natural_isZero < Builtin def call(nat) if nat.is_a?(Dhall::Natural) Dhall::Bool.new(value: nat.zero?) else super end end end class Natural_odd < Builtin def call(nat) if nat.is_a?(Dhall::Natural) Dhall::Bool.new(value: nat.odd?) else super end end end class Natural_show < Builtin def call(nat) if nat.is_a?(Dhall::Natural) Dhall::Text.new(value: nat.to_s) else super end end end class Natural_toInteger < Builtin def call(nat) if nat.is_a?(Dhall::Natural) Dhall::Integer.new(value: nat.value) else super end end end class List_build < Builtin include(ValueSemantics.for_attributes do type Either(nil, Expression), default: nil end) def fusion(*args) _, arg, = args if arg.is_a?(Application) && arg.function.is_a?(Application) && arg.function.function == List_fold.new arg.argument else super end end def call(arg) fill_or_call(arg) do arg.call( List.new.call(type), cons, EmptyList.new(element_type: type) ) end end protected def cons Function.of_arguments( type, List.new.call(type.shift(1, "_", 0)), body: Dhall::List.of(Variable["_", 1]).concat(Variable["_"]) ) end end class List_fold < Builtin include(ValueSemantics.for_attributes do ltype Either(nil, Expression), default: nil list Either(nil, List), default: nil ztype Either(nil, Expression), default: nil f Either(nil, Expression), default: nil end) def call(arg) fill_or_call(arg) do list.reduce(arg, &f).normalize end || super end end class List_head < Builtin include(ValueSemantics.for_attributes do type Either(nil, Expression), default: nil end) def call(arg) fill_or_call(arg) do if arg.is_a?(Dhall::List) arg.first else super end end end end class List_indexed < Builtin include(ValueSemantics.for_attributes do type Either(nil, Expression), default: nil end) def call(arg) fill_or_call(arg) do if arg.is_a?(Dhall::List) _call(arg) else super end end end protected def _call(arg) arg.map(type: indexed_type(type)) { |x, idx| Record.new( record: { "index" => Dhall::Natural.new(value: idx), "value" => x } ) }.normalize end def indexed_type(value_type) RecordType.new( record: { "index" => Natural.new, "value" => value_type } ) end end class List_last < Builtin include(ValueSemantics.for_attributes do type Either(nil, Expression), default: nil end) def call(arg) fill_or_call(arg) do if arg.is_a?(Dhall::List) arg.last else super end end end end class List_length < Builtin include(ValueSemantics.for_attributes do type Either(nil, Expression), default: nil end) def call(arg) fill_or_call(arg) do if arg.is_a?(Dhall::List) Dhall::Natural.new(value: arg.length) else super end end end end class List_reverse < Builtin include(ValueSemantics.for_attributes do type Either(nil, Expression), default: nil end) def call(arg) fill_or_call(arg) do if arg.is_a?(Dhall::List) arg.reverse else super end end end end class Optional_build < Builtin include(ValueSemantics.for_attributes do type Either(nil, Expression), default: nil end) def fusion(*args) _, arg, = args if arg.is_a?(Application) && arg.function.is_a?(Application) && arg.function.function == Optional_fold.new arg.argument else super end end def call(arg) fill_or_call(arg) do arg.call( Optional.new.call(type), some, OptionalNone.new(value_type: type) ) end end protected def some Function.of_arguments( type, body: Dhall::Optional.new( value: Variable["_"], value_type: type ) ) end end class Optional_fold < Builtin include(ValueSemantics.for_attributes do type Either(nil, Expression), default: nil optional Either(nil, Optional), default: nil ztype Either(nil, Expression), default: nil f Either(nil, Expression), default: nil end) def call(*args) args.reduce(self) do |fold, arg| fold.fill_or_call(arg) do fold.optional.reduce(arg, &fold.f) end || super end end end class Text_show < Builtin ENCODE = (Hash.new { |_, x| "\\u%04x" % x.ord }).merge( "\"" => "\\\"", "\\" => "\\\\", "\b" => "\\b", "\f" => "\\f", "\n" => "\\n", "\r" => "\\r", "\t" => "\\t" ) def call(arg) if arg.is_a?(Dhall::Text) Dhall::Text.new( value: "\"#{arg.value.gsub( /["\$\\\b\f\n\r\t\u0000-\u001F]/, &ENCODE )}\"" ) else super end end end class Bool < Builtin end class Optional < Builtin end class Natural < Builtin end class Integer < Builtin end class Double < Builtin end class Text < Builtin end class List < Builtin end class None < Builtin def call(arg) OptionalNone.new(value_type: arg) end end class Type < Builtin end class Kind < Builtin end class Sort < Builtin end # rubocop:enable Style/ClassAndModuleCamelCase def self.[](k) const = constants.find { |c| c.to_s.tr("_", "/").to_sym == k } const && const_get(const).new end end end