@@ 1,7 1,14 @@
# frozen_string_literal: true
+require "value_semantics"
+
module Dhall
- class Expression; end
+ class Expression
+ def map_subexpressions(&_)
+ # For expressions with no subexpressions
+ self
+ end
+ end
class Application < Expression
def initialize(f, *args)
@@ 12,6 19,10 @@ module Dhall
@f = f
@args = args
end
+
+ def map_subexpressions(&block)
+ self.class.new(block[@f], *@args.map(&block))
+ end
end
class Function < Expression
@@ 20,40 31,72 @@ module Dhall
@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
- def initialize(value)
- @value = value
+ include(ValueSemantics.for_attributes do
+ value Bool()
+ end)
+
+ def reduce(when_true, when_false)
+ value ? when_true : when_false
end
end
class Variable < Expression
- def initialize(var, index=0)
- @var = var
- @index = index
- end
+ include(ValueSemantics.for_attributes do
+ name String, default: "_"
+ index (0..Float::INFINITY), default: 0
+ end)
end
class Operator < Expression
- def initialize(op, lhs, rhs)
- @op = op
- @lhs = lhs
- @rhs = rhs
+ 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
- def initialize(*els)
- @els = els
+ include(ValueSemantics.for_attributes do
+ elements ArrayOf(Expression)
+ end)
+
+ def map_subexpressions(&block)
+ with(elements: elements.map(&block))
end
end
class EmptyList < List
- def initialize(type)
- @type = type
+ include(ValueSemantics.for_attributes do
+ type Expression
+ end)
+
+ def map_subexpressions(&block)
+ with(type: block[@type])
end
end
@@ 64,6 107,10 @@ module Dhall
@value = value
@type = type
end
+
+ def map_subexpressions(&block)
+ self.class.new(block[@value], block[@type])
+ end
end
class OptionalNone < Optional
@@ 72,6 119,10 @@ module Dhall
@type = type
end
+
+ def map_subexpressions(&block)
+ self.class.new(block[@type])
+ end
end
class Merge < Expression
@@ 80,18 131,34 @@ module Dhall
@input = input
@type = type
end
+
+ def map_subexpressions(&block)
+ self.class.new(block[@record], block[@input], block[@type])
+ end
end
class RecordType < Expression
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
end
class Record < 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 RecordFieldAccess < Expression
@@ 101,6 168,10 @@ module Dhall
@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
@@ 112,12 183,20 @@ module Dhall
@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
@@ 128,6 207,14 @@ module Dhall
@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
@@ 142,34 229,55 @@ module Dhall
end
class If < Expression
- def initialize(cond, thn, els)
- @cond = cond
- @thn = thn
- @els = els
+ 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
- def initialize(n)
- @n = n
- end
end
- class Natural < Number; end
- class Integer < Number; end
- class Double < Number; end
+ class Natural < Number
+ include(ValueSemantics.for_attributes do
+ value (0..Float::INFINITY)
+ end)
+ end
- class Text < Expression
- def initialize(string)
- raise TypeError, "must be a String" unless string.is_a?(String)
+ class Integer < Number
+ include(ValueSemantics.for_attributes do
+ value ::Integer
+ end)
+ end
- @string = string
- 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 < Text
- def initialize(*chunks)
- @chunks = chunks
+ class TextLiteral < Expression
+ include(ValueSemantics.for_attributes do
+ chunks ArrayOf(Expression)
+ end)
+
+ def map_subexpressions(&block)
+ with(chunks: chunks.map(&block))
end
end
@@ 226,6 334,10 @@ module Dhall
@assign = assign
@type = type
end
+
+ def map_subexpressions(&block)
+ self.class.new(@var, block[@assign], block[@type])
+ end
end
class LetBlock < Expression
@@ 237,6 349,13 @@ module Dhall
@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
@@ 244,5 363,9 @@ module Dhall
@value = value
@type = type
end
+
+ def map_subexpressions(&block)
+ self.class.new(block[@value], block[@type])
+ end
end
end
@@ 13,6 13,8 @@ module Dhall
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
@@ 20,66 22,21 @@ module Dhall
raise "Unknown expression: #{expression.inspect}"
end
- BINARY = {
- ::TrueClass => Bool.method(:decode),
- ::FalseClass => Bool.method(:decode),
- ::Float => Double.method(:decode),
- ::String => ->(e) { Variable.decode(e, 0) },
- ::Integer => ->(e) { Variable.decode("_", e) },
- ::Array => lambda { |e|
- if e.length == 2 && e.first.is_a?(::String)
- Variable.decode(*expression)
- else
- tag, *body = expression
- BINARY_TAGS[tag]&.decode(*body) ||
- (raise "Unknown expression: #{expression.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
-
class Expression
def self.decode(*args)
+ return new(value: args.first) if args.length == 1
+
new(*args)
end
end
- class Application < Expression
+ class Application
def self.decode(f, *args)
new(Dhall.decode(f), *args.map(&Dhall.method(:decode)))
end
end
- class Function < Expression
+ 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))
@@ 95,31 52,34 @@ module Dhall
end
end
- class Operator < Expression
- OPCODES = [
- :'||', :'&&', :==, :!=, :+, :*, :'++', :'#', :∧, :⫽, :⩓, :'?'
+ class Operator
+ OPERATORS = [
+ Or, And, Equal, NotEqual,
+ Plus, Times,
+ TextConcatenate, ListConcatenate,
+ RecordMerge, RecordOverride, RecordTypeMerge,
+ ImportFallback
].freeze
def self.decode(opcode, lhs, rhs)
- new(
- OPCODES[opcode] || (raise "Unknown opcode: #{opcode}"),
- Dhall.decode(lhs),
- Dhall.decode(rhs)
+ OPERATORS[opcode].new(
+ lhs: Dhall.decode(lhs),
+ rhs: Dhall.decode(rhs)
)
end
end
- class List < Expression
+ class List
def self.decode(type, *els)
if type.nil?
- List.new(*els.map(&Dhall.method(:decode)))
+ List.new(elements: els.map(&Dhall.method(:decode)))
else
- EmptyList.new(Dhall.decode(type))
+ EmptyList.new(type: Dhall.decode(type))
end
end
end
- class Optional < Expression
+ class Optional
def self.decode(type, value=nil)
if value.nil?
OptionalNone.new(Dhall.decode(type))
@@ 132,7 92,7 @@ module Dhall
end
end
- class Merge < Expression
+ class Merge
def self.decode(record, input, type=nil)
new(
Dhall.decode(record),
@@ 142,37 102,37 @@ module Dhall
end
end
- class RecordType < Expression
+ class RecordType
def self.decode(record)
new(Hash[record.map { |k, v| [k, Dhall.decode(v)] }])
end
end
- class Record < Expression
+ class Record
def self.decode(record)
new(Hash[record.map { |k, v| [k, Dhall.decode(v)] }])
end
end
- class RecordFieldAccess < Expression
+ class RecordFieldAccess
def self.decode(record, field)
new(Dhall.decode(record), field)
end
end
- class RecordProjection < Expression
+ class RecordProjection
def self.decode(record, *fields)
new(Dhall.decode(record), *fields)
end
end
- class UnionType < Expression
+ class UnionType
def self.decode(record)
new(Hash[record.map { |k, v| [k, Dhall.decode(v)] }])
end
end
- class Union < Expression
+ class Union
def self.decode(tag, value, rest_of_type)
new(
tag,
@@ 182,21 142,26 @@ module Dhall
end
end
- class If < Expression
- def self.decode(cond, thn, els)
- new(Dhall.decode(cond), Dhall.decode(thn), Dhall.decode(els))
+ 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 < Text
+ class TextLiteral
def self.decode(*chunks)
- if chunks.length == 1 && chunks.is_a?(String)
- Text.new(chunks.first)
- else
- TextLiteral.new(*chunks.map do |chunk|
- chunk.is_a?(String) ? Text.new(chunk) : Dhall.decode(chunk)
- end)
- end
+ 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
@@ 209,7 174,7 @@ module Dhall
].freeze
def self.decode(integrity_check, import_type, path_type, *parts)
- parts[0] = Dhall.decode(parts[0]) if path_type < 2 && !parts[0].nil?
+ parts[0] = Dhall.decode(parts[0]) if path_type
new(
integrity_check.nil? ? nil : IntegrityCheck.new(*integrity_check),
IMPORT_TYPES[import_type],
@@ 218,7 183,7 @@ module Dhall
end
end
- class LetBlock < Expression
+ class LetBlock
def self.decode(*parts)
new(
Dhall.decode(parts.pop),
@@ 233,9 198,56 @@ module Dhall
end
end
- class TypeAnnotation < Expression
+ 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