# frozen_string_literal: true
require "value_semantics"
require "dhall/util"
module Dhall
class Expression
def map_subexpressions(&_)
# For expressions with no subexpressions
self
end
def call(*args)
args.reduce(self) { |f, arg|
Application.new(function: f, arguments: [arg])
}.normalize
end
def to_proc
method(:call).to_proc
end
end
class Application < Expression
include(ValueSemantics.for_attributes do
function Expression
arguments Util::ArrayOf.new(Expression, min: 1)
end)
def map_subexpressions(&block)
with(function: block[function], arguments: arguments.map(&block))
end
end
class Function < Expression
def initialize(var, type, body)
@var = var
@type = type
@body = body
end
def map_subexpressions(&block)
self.class.new(@var, block[@type], block[@body])
end
end
class Forall < Function; end
class Bool < Expression
include(ValueSemantics.for_attributes do
value Bool()
end)
def reduce(when_true, when_false)
value ? when_true : when_false
end
end
class Variable < Expression
include(ValueSemantics.for_attributes do
name String, default: "_"
index (0..Float::INFINITY), default: 0
end)
end
class Operator < Expression
include(ValueSemantics.for_attributes do
lhs Expression
rhs Expression
end)
def map_subexpressions(&block)
with(lhs: block[@lhs], rhs: block[@rhs])
end
class Or < Operator; end
class And < Operator; end
class Equal < Operator; end
class NotEqual < Operator; end
class Plus < Operator; end
class Times < Operator; end
class TextConcatenate < Operator; end
class ListConcatenate < Operator; end
class RecordMerge < Operator; end
class RecordOverride < Operator; end
class RecordTypeMerge < Operator; end
class ImportFallback < Operator; end
end
class List < Expression
include(ValueSemantics.for_attributes do
elements ArrayOf(Expression)
end)
def map_subexpressions(&block)
with(elements: elements.map(&block))
end
def type
# TODO: inferred element type
end
def map(type: nil, &block)
with(elements: elements.each_with_index.map(&block))
end
def reduce(z)
elements.reverse.reduce(z) { |acc, x| yield x, acc }
end
def length
elements.length
end
def first
Optional.new(value: elements.first, type: type)
end
def last
Optional.new(value: elements.last, type: type)
end
def reverse
with(elements: elements.reverse)
end
end
class EmptyList < List
include(ValueSemantics.for_attributes do
type Expression
end)
def map_subexpressions(&block)
with(type: block[@type])
end
def map(type: nil)
type.nil? ? self : with(type: type)
end
def reduce(z)
z
end
def length
0
end
def first
OptionalNone.new(type: type)
end
def last
OptionalNone.new(type: type)
end
def reverse
self
end
end
class Optional < Expression
include(ValueSemantics.for_attributes do
value Expression
type Either(nil, Expression), default: nil
end)
def map_subexpressions(&block)
with(value: block[value], type: type.nil? ? type : block[type])
end
end
class OptionalNone < Optional
include(ValueSemantics.for_attributes do
type Expression
end)
def map_subexpressions(&block)
with(type: block[@type])
end
end
class Merge < Expression
def initialize(record, input, type)
@record = record
@input = input
@type = type
end
def map_subexpressions(&block)
self.class.new(block[@record], block[@input], block[@type])
end
end
class RecordType < Expression
attr_reader :record
def initialize(record)
@record = record
end
def map_subexpressions(&block)
self.class.new(
Hash[*@record.map { |k, v| [k, block[v]] }],
block[@input],
block[@type]
)
end
def eql?(other)
record == other.record
end
end
class Record < Expression
attr_reader :record
def initialize(record)
@record = record
end
def map_subexpressions(&block)
self.class.new(Hash[*@record.map { |k, v| [k, block[v]] }])
end
def eql?(other)
record == other.record
end
end
class RecordFieldAccess < Expression
def initialize(record, field)
raise TypeError, "field must be a String" unless field.is_a?(String)
@record = record
@field = field
end
def map_subexpressions(&block)
self.class.new(Hash[*@record.map { |k, v| [k, block[v]] }], @field)
end
end
class RecordProjection < Expression
def initialize(record, *fields)
unless fields.all? { |x| x.is_a?(String) }
raise TypeError, "fields must be String"
end
@record = record
@fields = fields
end
def map_subexpressions(&block)
self.class.new(Hash[*@record.map { |k, v| [k, block[v]] }], @fields)
end
end
class UnionType < Expression
def initialize(record)
@record = record
end
def map_subexpressions(&block)
self.class.new(Hash[*@record.map { |k, v| [k, block[v]] }])
end
end
class Union < Expression
def initialize(tag, value, rest_of_type)
raise TypeError, "tag must be a string" unless tag.is_a?(String)
@tag = tag
@value = value
@rest_of_type = rest_of_type
end
def map_subexpressions(&block)
self.class.new(
@tag,
block[@value],
Hash[*@rest_of_type.map { |k, v| [k, block[v]] }]
)
end
end
class Constructors < Expression
extend Gem::Deprecate
def initialize(arg)
@arg = arg
end
DEPRECATION_WIKI = "https://github.com/dhall-lang/dhall-lang/wiki/" \
"Migration:-Deprecation-of-constructors-keyword"
deprecate :initialize, DEPRECATION_WIKI, 2019, 4
end
class If < Expression
include(ValueSemantics.for_attributes do
predicate Expression
self.then Expression
self.else Expression
end)
def map_subexpressions(&block)
with(
predicate: block[predicate],
then: block[self.then],
else: block[self.else]
)
end
end
class Number < Expression
end
class Natural < Number
include(ValueSemantics.for_attributes do
value (0..Float::INFINITY)
end)
def to_s
value.to_s
end
def even?
value.even?
end
def odd?
value.odd?
end
def zero?
value.zero?
end
def pred
with(value: [0, value - 1].max)
end
end
class Integer < Number
include(ValueSemantics.for_attributes do
value ::Integer
end)
end
class Double < Number
include(ValueSemantics.for_attributes do
value ::Float
end)
end
class Text < Expression
include(ValueSemantics.for_attributes do
value ::String
end)
end
class TextLiteral < Expression
include(ValueSemantics.for_attributes do
chunks ArrayOf(Expression)
end)
def map_subexpressions(&block)
with(chunks: chunks.map(&block))
end
end
class Import < Expression
def initialize(integrity_check, import_type, path)
@integrity_check = integrity_check
@import_type = import_type
@path = path
end
class URI
def initialize(headers, authority, *path, query, fragment)
@headers = headers
@authority = authority
@path = path
@query = query
@fragment = fragment
end
end
class Http < URI; end
class Https < URI; end
class Path
def initialize(*path)
@path = path
end
end
class AbsolutePath < Path; end
class RelativePath < Path; end
class RelativeToParentPath < Path; end
class RelativeToHomePath < Path; end
class EnvironmentVariable
def initialize(var)
@var = var
end
end
class MissingImport; end
class IntegrityCheck
def initialize(protocol, data)
@protocol = protocol
@data = data
end
end
end
class Let
def initialize(var, assign, type=nil)
@var = var
@assign = assign
@type = type
end
def map_subexpressions(&block)
self.class.new(@var, block[@assign], block[@type])
end
end
class LetBlock < Expression
def initialize(body, *lets)
unless lets.all? { |x| x.is_a?(Let) }
raise TypeError, "LetBlock only contains Let"
end
@lets = lets
@body = body
end
def map_subexpressions(&block)
self.class.new(
block[@body],
*@lets.map { |let| let.map_subexpressions(&block) }
)
end
end
class TypeAnnotation < Expression
def initialize(value, type)
@value = value
@type = type
end
def map_subexpressions(&block)
self.class.new(block[@value], block[@type])
end
end
end