From b958e41b20a238798e611c516bc79c9f55ac93c1 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Tue, 9 Apr 2019 21:37:32 -0500 Subject: [PATCH] Add grab-bag of useful utils onto expressions --- .rubocop.yml | 3 + lib/dhall/as_dhall.rb | 4 - lib/dhall/ast.rb | 174 ++++++++++++++++++++++++++++++++++++++--- lib/dhall/normalize.rb | 42 ++++++++-- lib/dhall/resolve.rb | 10 +++ 5 files changed, 213 insertions(+), 20 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 1638a45..f92e9ed 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -65,6 +65,9 @@ Style/FormatString: Style/RegexpLiteral: AllowInnerSlashes: true +Style/SpecialGlobalVars: + EnforcedStyle: use_perl_names + Style/StringLiterals: EnforcedStyle: double_quotes diff --git a/lib/dhall/as_dhall.rb b/lib/dhall/as_dhall.rb index a03da72..d3607fe 100644 --- a/lib/dhall/as_dhall.rb +++ b/lib/dhall/as_dhall.rb @@ -2,10 +2,6 @@ require "ostruct" -require "dhall/ast" -require "dhall/binary" -require "dhall/typecheck" - module Dhall module AsDhall TAGS = { diff --git a/lib/dhall/ast.rb b/lib/dhall/ast.rb index 6de3d52..3923d77 100644 --- a/lib/dhall/ast.rb +++ b/lib/dhall/ast.rb @@ -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 diff --git a/lib/dhall/normalize.rb b/lib/dhall/normalize.rb index a4f757a..272d08b 100644 --- a/lib/dhall/normalize.rb +++ b/lib/dhall/normalize.rb @@ -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) diff --git a/lib/dhall/resolve.rb b/lib/dhall/resolve.rb index 088a7f8..5e6b4e1 100644 --- a/lib/dhall/resolve.rb +++ b/lib/dhall/resolve.rb @@ -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 -- 2.38.5