Include gem "morpher"
This commit is contained in:
parent
cf46fc693a
commit
12b8efc801
51 changed files with 3378 additions and 19 deletions
18
Gemfile.lock
18
Gemfile.lock
|
@ -11,7 +11,6 @@ PATH
|
||||||
equalizer (~> 0.0.9)
|
equalizer (~> 0.0.9)
|
||||||
ice_nine (~> 0.11.1)
|
ice_nine (~> 0.11.1)
|
||||||
memoizable (~> 0.4.2)
|
memoizable (~> 0.4.2)
|
||||||
morpher (~> 0.3.1)
|
|
||||||
parser (~> 2.7.2)
|
parser (~> 2.7.2)
|
||||||
procto (~> 0.0.2)
|
procto (~> 0.0.2)
|
||||||
regexp_parser (~> 1.8.2)
|
regexp_parser (~> 1.8.2)
|
||||||
|
@ -87,23 +86,6 @@ GEM
|
||||||
kwalify (0.7.2)
|
kwalify (0.7.2)
|
||||||
memoizable (0.4.2)
|
memoizable (0.4.2)
|
||||||
thread_safe (~> 0.3, >= 0.3.1)
|
thread_safe (~> 0.3, >= 0.3.1)
|
||||||
morpher (0.3.1)
|
|
||||||
abstract_type (~> 0.0.7)
|
|
||||||
adamantium (~> 0.2.0)
|
|
||||||
anima (~> 0.3.0)
|
|
||||||
ast (~> 2.2)
|
|
||||||
concord (~> 0.1.5)
|
|
||||||
equalizer (~> 0.0.9)
|
|
||||||
ice_nine (~> 0.11.0)
|
|
||||||
mprelude (~> 0.1.0)
|
|
||||||
procto (~> 0.0.2)
|
|
||||||
mprelude (0.1.0)
|
|
||||||
abstract_type (~> 0.0.7)
|
|
||||||
adamantium (~> 0.2.0)
|
|
||||||
concord (~> 0.1.5)
|
|
||||||
equalizer (~> 0.0.9)
|
|
||||||
ice_nine (~> 0.11.1)
|
|
||||||
procto (~> 0.0.2)
|
|
||||||
parallel (1.12.1)
|
parallel (1.12.1)
|
||||||
parser (2.7.2.0)
|
parser (2.7.2.0)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
|
|
111
lib/morpher.rb
Normal file
111
lib/morpher.rb
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
require 'abstract_type'
|
||||||
|
require 'concord'
|
||||||
|
require 'anima'
|
||||||
|
require 'ast'
|
||||||
|
require 'procto'
|
||||||
|
|
||||||
|
# Library namespace module
|
||||||
|
module Morpher
|
||||||
|
|
||||||
|
Undefined = Module.new.freeze
|
||||||
|
|
||||||
|
# Return evaluator from node
|
||||||
|
#
|
||||||
|
# @param [Node]
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.compile(node)
|
||||||
|
node = Compiler::Preprocessor::DEFAULT.call(node)
|
||||||
|
Compiler::Evaluator::DEFAULT.call(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return evaluate block to produce an AST node
|
||||||
|
#
|
||||||
|
# @return [AST::Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.sexp(&block)
|
||||||
|
NodeHelpers.module_eval(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Build morpher from yielding sexp blog
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.build(&block)
|
||||||
|
compile(sexp(&block))
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Morpher
|
||||||
|
|
||||||
|
require 'morpher/node_helpers'
|
||||||
|
require 'morpher/registry'
|
||||||
|
require 'morpher/printer'
|
||||||
|
require 'morpher/printer/mixin'
|
||||||
|
require 'morpher/evaluator'
|
||||||
|
require 'morpher/evaluator/nullary'
|
||||||
|
require 'morpher/evaluator/nullary/parameterized'
|
||||||
|
require 'morpher/evaluator/unary'
|
||||||
|
require 'morpher/evaluator/binary'
|
||||||
|
require 'morpher/evaluator/nary'
|
||||||
|
require 'morpher/evaluator/transformer'
|
||||||
|
require 'morpher/evaluator/transformer/block'
|
||||||
|
require 'morpher/evaluator/transformer/key'
|
||||||
|
require 'morpher/evaluator/transformer/guard'
|
||||||
|
require 'morpher/evaluator/transformer/attribute'
|
||||||
|
require 'morpher/evaluator/transformer/hash_transform'
|
||||||
|
require 'morpher/evaluator/transformer/map'
|
||||||
|
require 'morpher/evaluator/transformer/static'
|
||||||
|
require 'morpher/evaluator/transformer/input'
|
||||||
|
require 'morpher/evaluator/transformer/merge'
|
||||||
|
require 'morpher/evaluator/transformer/coerce'
|
||||||
|
require 'morpher/evaluator/transformer/custom'
|
||||||
|
require 'morpher/evaluator/transformer/domain'
|
||||||
|
require 'morpher/evaluator/transformer/domain/param'
|
||||||
|
require 'morpher/evaluator/transformer/domain/attribute_hash'
|
||||||
|
require 'morpher/evaluator/transformer/domain/instance_variables'
|
||||||
|
require 'morpher/evaluator/transformer/domain/attribute_accessors'
|
||||||
|
require 'morpher/evaluator/predicate'
|
||||||
|
require 'morpher/evaluator/predicate/eql'
|
||||||
|
require 'morpher/evaluator/predicate/primitive'
|
||||||
|
require 'morpher/evaluator/predicate/negation'
|
||||||
|
require 'morpher/evaluator/predicate/tautology'
|
||||||
|
require 'morpher/evaluator/predicate/contradiction'
|
||||||
|
require 'morpher/evaluator/predicate/boolean'
|
||||||
|
require 'morpher/evaluation'
|
||||||
|
require 'morpher/evaluation'
|
||||||
|
require 'morpher/type_lookup'
|
||||||
|
require 'morpher/compiler'
|
||||||
|
require 'morpher/compiler/error'
|
||||||
|
require 'morpher/compiler/emitter'
|
||||||
|
require 'morpher/compiler/evaluator'
|
||||||
|
require 'morpher/compiler/evaluator/emitter'
|
||||||
|
require 'morpher/compiler/preprocessor'
|
||||||
|
require 'morpher/compiler/preprocessor/emitter'
|
||||||
|
require 'morpher/compiler/preprocessor/emitter/noop'
|
||||||
|
require 'morpher/compiler/preprocessor/emitter/key'
|
||||||
|
require 'morpher/compiler/preprocessor/emitter/param'
|
||||||
|
require 'morpher/compiler/preprocessor/emitter/boolean'
|
||||||
|
require 'morpher/compiler/preprocessor/emitter/anima'
|
||||||
|
|
||||||
|
module Morpher
|
||||||
|
class Compiler
|
||||||
|
|
||||||
|
class Preprocessor
|
||||||
|
# Default preprocessor compiler
|
||||||
|
DEFAULT = new(Emitter::REGISTRY.freeze)
|
||||||
|
end # Preprocessor
|
||||||
|
|
||||||
|
class Evaluator
|
||||||
|
# Default evaluator compiler
|
||||||
|
DEFAULT = new(Morpher::Evaluator::REGISTRY, Emitter::REGISTRY.freeze)
|
||||||
|
end # Evaluator
|
||||||
|
|
||||||
|
end # Compiler
|
||||||
|
end # Morpher
|
17
lib/morpher/compiler.rb
Normal file
17
lib/morpher/compiler.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
module Morpher
|
||||||
|
# Abstract compiler base class
|
||||||
|
class Compiler
|
||||||
|
include AbstractType
|
||||||
|
|
||||||
|
# Call compiler
|
||||||
|
#
|
||||||
|
# @param [Node] node
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
abstract_method :call
|
||||||
|
|
||||||
|
end # Compiler
|
||||||
|
end # Morpher
|
82
lib/morpher/compiler/emitter.rb
Normal file
82
lib/morpher/compiler/emitter.rb
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
module Morpher
|
||||||
|
class Compiler
|
||||||
|
# Abstract target indepentand emitter
|
||||||
|
class Emitter
|
||||||
|
include AbstractType, Adamantium::Flat, NodeHelpers, Procto.call(:output)
|
||||||
|
extend NodeHelpers
|
||||||
|
|
||||||
|
# Return output of emitter
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
abstract_method :output
|
||||||
|
|
||||||
|
# Return node
|
||||||
|
#
|
||||||
|
# @return [Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
abstract_method :node
|
||||||
|
private :node
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return children
|
||||||
|
#
|
||||||
|
# @return [Array<AST::Node>]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def children
|
||||||
|
node.children
|
||||||
|
end
|
||||||
|
|
||||||
|
# Assert number of child nodes
|
||||||
|
#
|
||||||
|
# @return [self]
|
||||||
|
# if assertion is fullfilled
|
||||||
|
#
|
||||||
|
# @raise [NodeError]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def assert_children_amount(expected_amount)
|
||||||
|
actual_amount = children.length
|
||||||
|
fail Error::NodeChildren.new(node, expected_amount) unless actual_amount.equal?(expected_amount)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Name children
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
# rubocop:disable MethodLength
|
||||||
|
#
|
||||||
|
def self.children(*names)
|
||||||
|
names.each_with_index do |name, index|
|
||||||
|
define_method(name) do
|
||||||
|
children.at(index)
|
||||||
|
end
|
||||||
|
private name
|
||||||
|
end
|
||||||
|
|
||||||
|
define_method(:named_children) do
|
||||||
|
names
|
||||||
|
end
|
||||||
|
private :named_children
|
||||||
|
|
||||||
|
define_method(:remaining_children) do
|
||||||
|
children.drop(names.length)
|
||||||
|
end
|
||||||
|
private :remaining_children
|
||||||
|
end
|
||||||
|
private_class_method :children
|
||||||
|
|
||||||
|
end # Emitter
|
||||||
|
end # Compiler
|
||||||
|
end # Morpher
|
84
lib/morpher/compiler/error.rb
Normal file
84
lib/morpher/compiler/error.rb
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
module Morpher
|
||||||
|
class Compiler
|
||||||
|
|
||||||
|
# Abstract error class for compiler errors
|
||||||
|
class Error < RuntimeError
|
||||||
|
include AbstractType
|
||||||
|
|
||||||
|
# Error raised when node children have incorrect amount
|
||||||
|
class NodeChildren < self
|
||||||
|
include Concord.new(:node, :expected_amount)
|
||||||
|
|
||||||
|
# Return exception message
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def message
|
||||||
|
"Expected #{expected_amount} #{_children} for #{type}, got #{actual_amount}: #{children}"
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return inspected type
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def type
|
||||||
|
node.type.inspect
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return actual amount of children
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def actual_amount
|
||||||
|
children.length
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return children
|
||||||
|
#
|
||||||
|
# @return [Array]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def children
|
||||||
|
node.children
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return user firendly children message
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def _children
|
||||||
|
expected_amount.equal?(1) ? 'child' : 'children'
|
||||||
|
end
|
||||||
|
|
||||||
|
end # NodeChildren
|
||||||
|
|
||||||
|
# Error raised on compiling unknown nodes
|
||||||
|
class UnknownNode < self
|
||||||
|
include Concord.new(:type)
|
||||||
|
|
||||||
|
# Return exception error message
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def message
|
||||||
|
"Node type: #{type.inspect} is unknown"
|
||||||
|
end
|
||||||
|
|
||||||
|
end # UnknownNode
|
||||||
|
|
||||||
|
end # Error
|
||||||
|
end # Compiler
|
||||||
|
end # Morpher
|
63
lib/morpher/compiler/evaluator.rb
Normal file
63
lib/morpher/compiler/evaluator.rb
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
module Morpher
|
||||||
|
class Compiler
|
||||||
|
# Compiler with evaluators as output
|
||||||
|
class Evaluator < self
|
||||||
|
include Concord.new(:evaluators, :emitters)
|
||||||
|
|
||||||
|
# Return evaluator tree for node
|
||||||
|
#
|
||||||
|
# @param [Node] node
|
||||||
|
#
|
||||||
|
# @return [Evalautor]
|
||||||
|
# on success
|
||||||
|
#
|
||||||
|
# @raise [Compiler::Error]
|
||||||
|
# on error
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(node)
|
||||||
|
evaluator = evaluator(node)
|
||||||
|
emitter = emitter(evaluator)
|
||||||
|
emitter.call(self, evaluator, node)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Lookup evaluator for node
|
||||||
|
#
|
||||||
|
# @param [Node]
|
||||||
|
#
|
||||||
|
# @return [Class:Evaluator]
|
||||||
|
# if found
|
||||||
|
#
|
||||||
|
# @raise [Error::UnknownNode]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluator(node)
|
||||||
|
type = node.type
|
||||||
|
evaluators.fetch(type) do
|
||||||
|
fail Error::UnknownNode, type
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return emitter for evaluator
|
||||||
|
#
|
||||||
|
# @param [Class:Evalautor]
|
||||||
|
#
|
||||||
|
# @return [#call]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def emitter(evaluator)
|
||||||
|
emitters.each do |arity, emitter|
|
||||||
|
return emitter if evaluator.ancestors.include?(arity)
|
||||||
|
end
|
||||||
|
fail Error::UnknownNode, evaluator
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Evaluator
|
||||||
|
end # Compiler
|
||||||
|
end # Morpher
|
224
lib/morpher/compiler/evaluator/emitter.rb
Normal file
224
lib/morpher/compiler/evaluator/emitter.rb
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
module Morpher
|
||||||
|
class Compiler
|
||||||
|
class Evaluator
|
||||||
|
# Emitter for evaluators
|
||||||
|
class Emitter < Compiler::Emitter
|
||||||
|
include Registry, Concord.new(:compiler, :evaluator_klass, :node)
|
||||||
|
|
||||||
|
# Return output
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def output
|
||||||
|
validate_node
|
||||||
|
evaluator
|
||||||
|
end
|
||||||
|
memoize :output
|
||||||
|
|
||||||
|
# Validate node
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
# if successful
|
||||||
|
#
|
||||||
|
# @raise [Error]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
abstract_method :validate_node
|
||||||
|
private :validate_node
|
||||||
|
|
||||||
|
# Return evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
abstract_method :evaluator
|
||||||
|
private :evaluator
|
||||||
|
|
||||||
|
# Emitter for nullary non parameterized evaluators
|
||||||
|
class Nullary < self
|
||||||
|
register Morpher::Evaluator::Nullary
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return output
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluator
|
||||||
|
evaluator_klass.new
|
||||||
|
end
|
||||||
|
|
||||||
|
# Validate node
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
# if successful
|
||||||
|
#
|
||||||
|
# @raise [Error]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def validate_node
|
||||||
|
assert_children_amount(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Emitter for nullary parameterized evaluators
|
||||||
|
class Parameterized < self
|
||||||
|
register Morpher::Evaluator::Nullary::Parameterized
|
||||||
|
|
||||||
|
children :param
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return output
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluator
|
||||||
|
evaluator_klass.new(effective_param)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return effective param
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def effective_param
|
||||||
|
if param.kind_of?(AST::Node) && param.type.equal?(:raw) && param.children.length.equal?(1)
|
||||||
|
param.children.first
|
||||||
|
else
|
||||||
|
param
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Validate node
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
# if successful
|
||||||
|
#
|
||||||
|
# @raise [Error]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def validate_node
|
||||||
|
assert_children_amount(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Paramterized
|
||||||
|
end # Nullary
|
||||||
|
|
||||||
|
# Emitter for unary evaluators
|
||||||
|
class Unary < self
|
||||||
|
register Morpher::Evaluator::Unary
|
||||||
|
children :operand
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluator
|
||||||
|
evaluator_klass.new(compiler.call(operand))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Validate node
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
# if successful
|
||||||
|
#
|
||||||
|
# @raise [Error]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def validate_node
|
||||||
|
assert_children_amount(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Unary
|
||||||
|
|
||||||
|
# Emitter for unary evaluators
|
||||||
|
class Binary < self
|
||||||
|
register Morpher::Evaluator::Binary
|
||||||
|
children :left, :right
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluator
|
||||||
|
evaluator_klass.new(
|
||||||
|
compiler.call(left),
|
||||||
|
compiler.call(right)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Validate node
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
# if successful
|
||||||
|
#
|
||||||
|
# @raise [Error]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def validate_node
|
||||||
|
assert_children_amount(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Unary
|
||||||
|
|
||||||
|
# Emitter for nary evaluators
|
||||||
|
class Nary < self
|
||||||
|
register Morpher::Evaluator::Nary
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluator
|
||||||
|
evaluator_klass.new(children.map(&compiler.method(:call)))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Validate node
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
# if successful
|
||||||
|
#
|
||||||
|
# @raise [Error]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def validate_node
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Nary
|
||||||
|
|
||||||
|
end # Emitter
|
||||||
|
end # Evaluator
|
||||||
|
end # Compiler
|
||||||
|
end # Morpher
|
29
lib/morpher/compiler/preprocessor.rb
Normal file
29
lib/morpher/compiler/preprocessor.rb
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
module Morpher
|
||||||
|
class Compiler
|
||||||
|
# AST preprocessor
|
||||||
|
class Preprocessor < self
|
||||||
|
include Concord.new(:emitters)
|
||||||
|
|
||||||
|
# Call preprocessor
|
||||||
|
#
|
||||||
|
# @param [Node] node
|
||||||
|
# the raw AST node after DSL
|
||||||
|
#
|
||||||
|
# @return [Node]
|
||||||
|
# the transformed ast node
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(node)
|
||||||
|
loop do
|
||||||
|
emitter = emitters.fetch(node.type, Emitter::Noop)
|
||||||
|
node = emitter.call(self, node)
|
||||||
|
break if emitter.equal?(Emitter::Noop)
|
||||||
|
end
|
||||||
|
|
||||||
|
node
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Preprocessor
|
||||||
|
end # Compiler
|
||||||
|
end # Morpher
|
54
lib/morpher/compiler/preprocessor/emitter.rb
Normal file
54
lib/morpher/compiler/preprocessor/emitter.rb
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
module Morpher
|
||||||
|
class Compiler
|
||||||
|
class Preprocessor
|
||||||
|
# Abstract preprocessor emitter
|
||||||
|
class Emitter < Compiler::Emitter
|
||||||
|
include Registry, Concord.new(:preprocessor, :node)
|
||||||
|
|
||||||
|
# Return output
|
||||||
|
#
|
||||||
|
# @return [AST::Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def output
|
||||||
|
validate_node
|
||||||
|
processed_node
|
||||||
|
end
|
||||||
|
memoize :output
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Visit node
|
||||||
|
#
|
||||||
|
# @param [Node] node
|
||||||
|
# original untransformed node
|
||||||
|
#
|
||||||
|
# @return [Node]
|
||||||
|
# transformed node
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def visit(node)
|
||||||
|
preprocessor.call(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Validate node
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
# if successful
|
||||||
|
#
|
||||||
|
# @raise [Error]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def validate_node
|
||||||
|
assert_children_amount(named_children.length)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Emitter
|
||||||
|
|
||||||
|
end # Preprocessor
|
||||||
|
end # Compiler
|
||||||
|
end # Morpher
|
69
lib/morpher/compiler/preprocessor/emitter/anima.rb
Normal file
69
lib/morpher/compiler/preprocessor/emitter/anima.rb
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
module Morpher
|
||||||
|
class Compiler
|
||||||
|
class Preprocessor
|
||||||
|
class Emitter
|
||||||
|
# Abstract base class for anima emitters
|
||||||
|
class Anima < self
|
||||||
|
include AbstractType
|
||||||
|
|
||||||
|
children :model
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return domain param
|
||||||
|
#
|
||||||
|
# @return [Transformer::Domain::Param]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def param
|
||||||
|
Morpher::Evaluator::Transformer::Domain::Param.new(
|
||||||
|
model,
|
||||||
|
model.anima.attribute_names
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
class Dump < self
|
||||||
|
|
||||||
|
register :anima_dump
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return transformed node
|
||||||
|
#
|
||||||
|
# @param [Node] node
|
||||||
|
#
|
||||||
|
# @return [Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def processed_node
|
||||||
|
s(:dump_attribute_hash, param)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Dump
|
||||||
|
|
||||||
|
class Load < self
|
||||||
|
|
||||||
|
register :anima_load
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return transformed node
|
||||||
|
#
|
||||||
|
# @param [Node] node
|
||||||
|
#
|
||||||
|
# @return [Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def processed_node
|
||||||
|
s(:load_attribute_hash, param)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Load
|
||||||
|
end # Anima
|
||||||
|
end # Emitter
|
||||||
|
end # Preprocessor
|
||||||
|
end # Compiler
|
||||||
|
end # Morpher
|
31
lib/morpher/compiler/preprocessor/emitter/boolean.rb
Normal file
31
lib/morpher/compiler/preprocessor/emitter/boolean.rb
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
module Morpher
|
||||||
|
class Compiler
|
||||||
|
class Preprocessor
|
||||||
|
class Emitter
|
||||||
|
# Preprocessor for boolean primitive
|
||||||
|
class Boolean < self
|
||||||
|
register :boolean
|
||||||
|
|
||||||
|
children
|
||||||
|
|
||||||
|
NODE = s(:xor, s(:primitive, TrueClass), s(:primitive, FalseClass))
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return transformed node
|
||||||
|
#
|
||||||
|
# @param [Node] node
|
||||||
|
#
|
||||||
|
# @return [Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def processed_node
|
||||||
|
NODE
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Boolean
|
||||||
|
end # Emitter
|
||||||
|
end # Preprocessor
|
||||||
|
end # Compiler
|
||||||
|
end # Morpher
|
87
lib/morpher/compiler/preprocessor/emitter/key.rb
Normal file
87
lib/morpher/compiler/preprocessor/emitter/key.rb
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
module Morpher
|
||||||
|
class Compiler
|
||||||
|
class Preprocessor
|
||||||
|
class Emitter
|
||||||
|
|
||||||
|
# Namespace class for key preprocessors
|
||||||
|
class Key < self
|
||||||
|
include AbstractType
|
||||||
|
|
||||||
|
# Key symbolization preprocessor
|
||||||
|
class Symbolize < self
|
||||||
|
|
||||||
|
register :key_symbolize
|
||||||
|
|
||||||
|
children :key, :operand
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return transformed node
|
||||||
|
#
|
||||||
|
# @param [Node] node
|
||||||
|
#
|
||||||
|
# @return [Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def processed_node
|
||||||
|
s(:key_transform, key.to_s, key.to_sym, operand)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Symbolize
|
||||||
|
|
||||||
|
# Neutral key preprocessor
|
||||||
|
class Neutral < self
|
||||||
|
|
||||||
|
register :key
|
||||||
|
|
||||||
|
children :key, :operand
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return transformed node
|
||||||
|
#
|
||||||
|
# @param [Node] node
|
||||||
|
#
|
||||||
|
# @return [Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def processed_node
|
||||||
|
s(:key_transform, key, key, operand)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Neutral
|
||||||
|
|
||||||
|
# Key transformation preprocessor
|
||||||
|
class Transform < self
|
||||||
|
register :key_transform
|
||||||
|
|
||||||
|
children :from, :to, :operand
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return transformed node
|
||||||
|
#
|
||||||
|
# @param [Node] node
|
||||||
|
#
|
||||||
|
# @return [Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def processed_node
|
||||||
|
s(
|
||||||
|
:block,
|
||||||
|
s(:key_fetch, from),
|
||||||
|
visit(operand),
|
||||||
|
s(:key_dump, to)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Transform
|
||||||
|
|
||||||
|
end # Key
|
||||||
|
end # Emitter
|
||||||
|
end # Preprocessor
|
||||||
|
end # Compiler
|
||||||
|
end # Morpher
|
45
lib/morpher/compiler/preprocessor/emitter/noop.rb
Normal file
45
lib/morpher/compiler/preprocessor/emitter/noop.rb
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
module Morpher
|
||||||
|
class Compiler
|
||||||
|
class Preprocessor
|
||||||
|
class Emitter
|
||||||
|
|
||||||
|
# Noop emitter just descending into children
|
||||||
|
class Noop < self
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return output
|
||||||
|
#
|
||||||
|
# @return [Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def processed_node
|
||||||
|
mapped_children = node.children.map do |child|
|
||||||
|
if child.kind_of?(node.class)
|
||||||
|
visit(child)
|
||||||
|
else
|
||||||
|
child
|
||||||
|
end
|
||||||
|
end
|
||||||
|
s(node.type, *mapped_children)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Validate node
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
# if successful
|
||||||
|
#
|
||||||
|
# @raise [Error]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def validate_node
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Noop
|
||||||
|
end # Emitter
|
||||||
|
end # Preprocessor
|
||||||
|
end # Compiler
|
||||||
|
end # Morpher
|
50
lib/morpher/compiler/preprocessor/emitter/param.rb
Normal file
50
lib/morpher/compiler/preprocessor/emitter/param.rb
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
module Morpher
|
||||||
|
class Compiler
|
||||||
|
class Preprocessor
|
||||||
|
class Emitter
|
||||||
|
|
||||||
|
# Param domain transformer specific emitter
|
||||||
|
class Param < self
|
||||||
|
|
||||||
|
register :param
|
||||||
|
|
||||||
|
children :model
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return output
|
||||||
|
#
|
||||||
|
# @return [Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def processed_node
|
||||||
|
param = Morpher::Evaluator::Transformer::Domain::Param.new(
|
||||||
|
model,
|
||||||
|
remaining_children
|
||||||
|
)
|
||||||
|
s(:raw, param)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Validate node
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
# if successful
|
||||||
|
#
|
||||||
|
# @raise [Error]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def validate_node
|
||||||
|
remaining_children.each_with_index do |child, index|
|
||||||
|
next if child.kind_of?(Symbol)
|
||||||
|
fail Error::ChildType, Symbol, child, index
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Noop
|
||||||
|
end # Emitter
|
||||||
|
end # Preprocessor
|
||||||
|
end # Compiler
|
||||||
|
end # Morpher
|
118
lib/morpher/evaluation.rb
Normal file
118
lib/morpher/evaluation.rb
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
module Morpher
|
||||||
|
# Abstract namespace class for evaluation states
|
||||||
|
class Evaluation
|
||||||
|
include AbstractType, Printer::Mixin, Adamantium::Flat, Anima.new(
|
||||||
|
:evaluator,
|
||||||
|
:input,
|
||||||
|
:output,
|
||||||
|
:success
|
||||||
|
)
|
||||||
|
|
||||||
|
private :success
|
||||||
|
|
||||||
|
# Test if evaluation was successful
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
# if evaluation was successful
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
alias_method :success?, :success
|
||||||
|
public :success?
|
||||||
|
|
||||||
|
ERROR_DEFAULTS = IceNine.deep_freeze(
|
||||||
|
output: Undefined,
|
||||||
|
success: false
|
||||||
|
)
|
||||||
|
|
||||||
|
SUCCESS_DEFAULTS = IceNine.deep_freeze(
|
||||||
|
success: true
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return error instance
|
||||||
|
#
|
||||||
|
# @param [Hash<Symbol, Object>] attributes
|
||||||
|
#
|
||||||
|
# @return [Evaluation]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.error(attributes)
|
||||||
|
new(ERROR_DEFAULTS.merge(attributes))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return successful instance
|
||||||
|
#
|
||||||
|
# @param [Hash<Symbol, Object>] attributes
|
||||||
|
#
|
||||||
|
# @return [Evaluation]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.success(attributes)
|
||||||
|
new(SUCCESS_DEFAULTS.merge(attributes))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Evaluation state for nullary evaluators
|
||||||
|
class Nullary < self
|
||||||
|
|
||||||
|
printer do
|
||||||
|
name
|
||||||
|
indent do
|
||||||
|
attributes :input, :output, :success?
|
||||||
|
visit :evaluator
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# Evaluation state for nary evaluators
|
||||||
|
class Nary < self
|
||||||
|
include anima.add(:evaluations)
|
||||||
|
|
||||||
|
printer do
|
||||||
|
name
|
||||||
|
indent do
|
||||||
|
attributes :input, :output, :success?
|
||||||
|
attribute_class :evaluator
|
||||||
|
visit_many :evaluations
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Evaluation
|
||||||
|
|
||||||
|
# Evaluation state for unary evaluators
|
||||||
|
class Binary < self
|
||||||
|
include anima.add(:left_evaluation, :right_evaluation)
|
||||||
|
|
||||||
|
printer do
|
||||||
|
name
|
||||||
|
indent do
|
||||||
|
attributes :input, :output, :success?
|
||||||
|
visit :left_evaluation
|
||||||
|
visit :right_evaluation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Unary
|
||||||
|
|
||||||
|
# Evaluation state for unary evaluators
|
||||||
|
class Unary < self
|
||||||
|
include anima.add(:operand_evaluation)
|
||||||
|
|
||||||
|
printer do
|
||||||
|
name
|
||||||
|
indent do
|
||||||
|
attributes :input, :output, :success?
|
||||||
|
visit :operand_evaluation
|
||||||
|
visit :evaluator
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Unary
|
||||||
|
|
||||||
|
end # Evaluation
|
||||||
|
end # Morpher
|
40
lib/morpher/evaluator.rb
Normal file
40
lib/morpher/evaluator.rb
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
module Morpher
|
||||||
|
|
||||||
|
# Abstract namespace class for non tracking evaluators
|
||||||
|
class Evaluator
|
||||||
|
include Adamantium::Flat,
|
||||||
|
Registry,
|
||||||
|
AbstractType,
|
||||||
|
Printer::Mixin,
|
||||||
|
NodeHelpers
|
||||||
|
|
||||||
|
# Call evaluator in non tracking mode
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
abstract_method :call
|
||||||
|
|
||||||
|
# Call evaluator in tracking mode
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Evaluation]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
abstract_method :evaluation
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
abstract_method :inverse
|
||||||
|
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
46
lib/morpher/evaluator/binary.rb
Normal file
46
lib/morpher/evaluator/binary.rb
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
module Morpher
|
||||||
|
|
||||||
|
class Evaluator
|
||||||
|
|
||||||
|
# Mixin for binary evaluators
|
||||||
|
module Binary
|
||||||
|
CONCORD = Concord::Public.new(:left, :right)
|
||||||
|
|
||||||
|
PRINTER = lambda do |_|
|
||||||
|
name
|
||||||
|
indent do
|
||||||
|
visit(:left)
|
||||||
|
visit(:right)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return node
|
||||||
|
#
|
||||||
|
# @return [AST::Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def node
|
||||||
|
s(type, left.node, right.node)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Hook called when module gets included
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.included(descendant)
|
||||||
|
descendant.class_eval do
|
||||||
|
include CONCORD
|
||||||
|
printer(&PRINTER)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :included
|
||||||
|
|
||||||
|
end # Nary
|
||||||
|
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
97
lib/morpher/evaluator/nary.rb
Normal file
97
lib/morpher/evaluator/nary.rb
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
|
||||||
|
# Mixin for nary evaluators
|
||||||
|
module Nary
|
||||||
|
CONCORD = Concord::Public.new(:body)
|
||||||
|
|
||||||
|
PRINTER = lambda do |_|
|
||||||
|
name
|
||||||
|
indent do
|
||||||
|
visit_many(:body)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return AST
|
||||||
|
#
|
||||||
|
# @return [AST::Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def node
|
||||||
|
s(type, *body.map(&:node))
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return positive evaluation
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
# @param [Array<Evaluation>] evaluations
|
||||||
|
#
|
||||||
|
# @return [Evaluation]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluation_positive(input, evaluations)
|
||||||
|
Evaluation::Nary.success(
|
||||||
|
evaluator: self,
|
||||||
|
input: input,
|
||||||
|
output: true,
|
||||||
|
evaluations: evaluations
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return negative evaluation
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
# @param [Array<Evaluation>] evaluations
|
||||||
|
#
|
||||||
|
# @return [Evaluation]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluation_negative(input, evaluations)
|
||||||
|
Evaluation::Nary.success(
|
||||||
|
evaluator: self,
|
||||||
|
input: input,
|
||||||
|
output: false,
|
||||||
|
evaluations: evaluations
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return evaluation error
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
# @param [Array<Evaluation>] evaluations
|
||||||
|
#
|
||||||
|
# @return [Evaluation]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluation_error(input, evaluations)
|
||||||
|
Evaluation::Nary.error(
|
||||||
|
evaluator: self,
|
||||||
|
input: input,
|
||||||
|
evaluations: evaluations
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Hook called when module gets included
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.included(descendant)
|
||||||
|
descendant.class_eval do
|
||||||
|
include CONCORD
|
||||||
|
printer(&PRINTER)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :included
|
||||||
|
|
||||||
|
end # Nary
|
||||||
|
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
92
lib/morpher/evaluator/nullary.rb
Normal file
92
lib/morpher/evaluator/nullary.rb
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
# Mixin to define nullary evaluators
|
||||||
|
module Nullary
|
||||||
|
|
||||||
|
CONCORD = Concord::Public.new
|
||||||
|
|
||||||
|
PRINTER = lambda do |_|
|
||||||
|
name
|
||||||
|
end
|
||||||
|
|
||||||
|
# Instance methods for nullary evaluators
|
||||||
|
module InstanceMethods
|
||||||
|
|
||||||
|
# Return default successful evaluation
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Evaluation]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluation(input)
|
||||||
|
evaluation_success(input, call(input))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return node
|
||||||
|
#
|
||||||
|
# @return [AST::Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def node
|
||||||
|
s(type)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return evaluation error for input
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Evaluation]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluation_error(input)
|
||||||
|
Evaluation::Nullary.new(
|
||||||
|
evaluator: self,
|
||||||
|
input: input,
|
||||||
|
output: Undefined,
|
||||||
|
success: false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return evaluation success for input and output
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
# @param [Object] output
|
||||||
|
#
|
||||||
|
# @return [Evaluation]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluation_success(input, output)
|
||||||
|
Evaluation::Nullary.new(
|
||||||
|
evaluator: self,
|
||||||
|
input: input,
|
||||||
|
output: output,
|
||||||
|
success: true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # InstanceMethods
|
||||||
|
|
||||||
|
# Hook called when module gets included
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.included(descendant)
|
||||||
|
descendant.class_eval do
|
||||||
|
include InstanceMethods, CONCORD
|
||||||
|
printer(&PRINTER)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :included
|
||||||
|
|
||||||
|
end # Nullary
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
48
lib/morpher/evaluator/nullary/parameterized.rb
Normal file
48
lib/morpher/evaluator/nullary/parameterized.rb
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
module Nullary
|
||||||
|
# Mixin to define parameterized nullary evaluators
|
||||||
|
module Parameterized
|
||||||
|
|
||||||
|
CONCORD = Concord::Public.new(:param)
|
||||||
|
|
||||||
|
PRINTER = lambda do |_|
|
||||||
|
name
|
||||||
|
indent do
|
||||||
|
attribute :param
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Mixin for nullary parameterized evaluators
|
||||||
|
module InstanceMethods
|
||||||
|
|
||||||
|
# Return node
|
||||||
|
#
|
||||||
|
# @return [AST::Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def node
|
||||||
|
s(type, param)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # InstanceMethods
|
||||||
|
|
||||||
|
# Hook called when module gets included
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.included(descendant)
|
||||||
|
descendant.class_eval do
|
||||||
|
include InstanceMethods, Nullary::InstanceMethods, CONCORD
|
||||||
|
printer(&PRINTER)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :included
|
||||||
|
|
||||||
|
end # Nullary
|
||||||
|
end # Parameterized
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
22
lib/morpher/evaluator/predicate.rb
Normal file
22
lib/morpher/evaluator/predicate.rb
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
# Abstract namespace class for predicate evaluators
|
||||||
|
class Predicate < self
|
||||||
|
include Transformer::Intransitive
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# This is a very naive implementation.
|
||||||
|
# Subclasses can do a more elaborated choice.
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
Negation.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Predicate
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
76
lib/morpher/evaluator/predicate/boolean.rb
Normal file
76
lib/morpher/evaluator/predicate/boolean.rb
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Predicate
|
||||||
|
# Evaluator for nary boolean predicates
|
||||||
|
class Boolean < self
|
||||||
|
include Nary
|
||||||
|
|
||||||
|
# Call evaluator with input
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Boolean]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
body .public_send(
|
||||||
|
self.class::ENUMERABLE_METHOD
|
||||||
|
) { |evaluator| evaluator.call(input) }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return evaluation for input
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Evaluation::Nary]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluation(input)
|
||||||
|
klass = self.class
|
||||||
|
|
||||||
|
evaluations = body.each_with_object([]) do |evaluator, aggregate|
|
||||||
|
evaluation = evaluator.evaluation(input)
|
||||||
|
aggregate << evaluation
|
||||||
|
next if evaluation.output.equal?(klass::OUTPUT_EXPECTATION)
|
||||||
|
return send(klass::ERROR_METHOD, input, aggregate)
|
||||||
|
end
|
||||||
|
|
||||||
|
send(klass::SUCCESS_METHOD, input, evaluations)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Evaluator for nary and predicates
|
||||||
|
class And < self
|
||||||
|
register :and
|
||||||
|
|
||||||
|
ENUMERABLE_METHOD = :all?
|
||||||
|
OUTPUT_EXPECTATION = true
|
||||||
|
ERROR_METHOD = :evaluation_negative
|
||||||
|
SUCCESS_METHOD = :evaluation_positive
|
||||||
|
end # And
|
||||||
|
|
||||||
|
# Evaluator for nary or predicates
|
||||||
|
class Or < self
|
||||||
|
register :or
|
||||||
|
|
||||||
|
ENUMERABLE_METHOD = :any?
|
||||||
|
OUTPUT_EXPECTATION = false
|
||||||
|
ERROR_METHOD = :evaluation_positive
|
||||||
|
SUCCESS_METHOD = :evaluation_negative
|
||||||
|
end # Or
|
||||||
|
|
||||||
|
# Evaluator for nary xor predicates
|
||||||
|
class Xor < self
|
||||||
|
register :xor
|
||||||
|
|
||||||
|
ENUMERABLE_METHOD = :one?
|
||||||
|
OUTPUT_EXPECTATION = false
|
||||||
|
ERROR_METHOD = :evaluation_positive
|
||||||
|
SUCCESS_METHOD = :evaluation_negative
|
||||||
|
end # Xor
|
||||||
|
|
||||||
|
end # Boolean
|
||||||
|
end # Predicate
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
36
lib/morpher/evaluator/predicate/contradiction.rb
Normal file
36
lib/morpher/evaluator/predicate/contradiction.rb
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Predicate
|
||||||
|
|
||||||
|
# Evaluator for contradiction
|
||||||
|
class Contradiction < self
|
||||||
|
include Nullary
|
||||||
|
|
||||||
|
register :false
|
||||||
|
|
||||||
|
# Call predicate evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] _input
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(_input)
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
Tautology.new
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Contradiction
|
||||||
|
end # Predicate
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
50
lib/morpher/evaluator/predicate/eql.rb
Normal file
50
lib/morpher/evaluator/predicate/eql.rb
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Predicate
|
||||||
|
# Binary equal evaluator
|
||||||
|
class EQL < self
|
||||||
|
include Binary
|
||||||
|
|
||||||
|
register :eql
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
# if input is semantically equivalent to expectation
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
left.call(input).eql?(right.call(input))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return evaluation
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Evaluation]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluation(input)
|
||||||
|
left_evaluation = left.evaluation(input)
|
||||||
|
right_evaluation = right.evaluation(input)
|
||||||
|
|
||||||
|
Evaluation::Binary.success(
|
||||||
|
evaluator: self,
|
||||||
|
input: input,
|
||||||
|
output: left_evaluation.output.eql?(right_evaluation.output),
|
||||||
|
left_evaluation: left_evaluation,
|
||||||
|
right_evaluation: right_evaluation
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # EQL
|
||||||
|
end # Predicate
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
52
lib/morpher/evaluator/predicate/negation.rb
Normal file
52
lib/morpher/evaluator/predicate/negation.rb
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Predicate
|
||||||
|
# Predicate negation
|
||||||
|
class Negation < self
|
||||||
|
include Unary
|
||||||
|
|
||||||
|
register :negate
|
||||||
|
|
||||||
|
# Return evaluation for input
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Evaluation]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluation(input)
|
||||||
|
operand_output = operand.call(input)
|
||||||
|
evaluation_success(input, operand_output, !operand_output)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
# if input NOT evaluated to true under operand
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
!operand.call(input)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
operand
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Negation
|
||||||
|
end # Predicate
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
49
lib/morpher/evaluator/predicate/primitive.rb
Normal file
49
lib/morpher/evaluator/predicate/primitive.rb
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Predicate
|
||||||
|
# Abstract namespace class for predicate evaluators on primitives
|
||||||
|
class Primitive < self
|
||||||
|
include Nullary::Parameterized
|
||||||
|
|
||||||
|
# Evaluator for exact primitive match
|
||||||
|
class Exact < self
|
||||||
|
|
||||||
|
register :primitive
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] object
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
# if object's type is #equal?
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(object)
|
||||||
|
object.class.equal?(param)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Exact
|
||||||
|
|
||||||
|
# Evaluator for permissive primtivie match
|
||||||
|
class Permissive < self
|
||||||
|
register :is_a
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] object
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
# if objects type equals exactly
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(object)
|
||||||
|
object.kind_of?(param)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Permissive
|
||||||
|
end # Primitive
|
||||||
|
end # Predicate
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
36
lib/morpher/evaluator/predicate/tautology.rb
Normal file
36
lib/morpher/evaluator/predicate/tautology.rb
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Predicate
|
||||||
|
|
||||||
|
# Evaluator for trautology
|
||||||
|
class Tautology < self
|
||||||
|
include Nullary
|
||||||
|
|
||||||
|
register :true
|
||||||
|
|
||||||
|
# Call predicate evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] _input
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(_input)
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
Contradiction.new
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Tautology
|
||||||
|
end # Predicate
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
75
lib/morpher/evaluator/transformer.rb
Normal file
75
lib/morpher/evaluator/transformer.rb
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
# Abstract namespace class for transforming evaluators
|
||||||
|
class Transformer < self
|
||||||
|
include AbstractType
|
||||||
|
|
||||||
|
# Error raised when transformation cannot continue
|
||||||
|
class TransformError < RuntimeError
|
||||||
|
include Concord.new(:transformer, :input)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test evaluator transformer is transitive
|
||||||
|
#
|
||||||
|
# A transitive evaluator allows to inverse an operation
|
||||||
|
# via its #inverse evaluator.
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
# if transformer is transitive
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
abstract_method :transitive?
|
||||||
|
|
||||||
|
# Mixin for evaluators that are transitive by definition
|
||||||
|
module Transitive
|
||||||
|
|
||||||
|
# Test if evaluator is transitive
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def transitive?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Intransitive
|
||||||
|
|
||||||
|
# Mixin for evaluators that are intransitive by definition
|
||||||
|
module Intransitive
|
||||||
|
|
||||||
|
# Test if evaluator is transitive
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def transitive?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Intransitive
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Raise transform error
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @raise [TransformError]
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def raise_transform_error(input)
|
||||||
|
fail TransformError.new(self, input)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Transform
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
25
lib/morpher/evaluator/transformer/attribute.rb
Normal file
25
lib/morpher/evaluator/transformer/attribute.rb
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Transformer
|
||||||
|
# Transformer to return a specific attribute of input
|
||||||
|
class Attribute < self
|
||||||
|
include Nullary::Parameterized, Intransitive
|
||||||
|
|
||||||
|
register :attribute
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
input.public_send(param)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Attribute
|
||||||
|
end # Transformer
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
81
lib/morpher/evaluator/transformer/block.rb
Normal file
81
lib/morpher/evaluator/transformer/block.rb
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Transformer
|
||||||
|
|
||||||
|
# Evaluator to perform n transformations in a row
|
||||||
|
class Block < self
|
||||||
|
include Nary
|
||||||
|
|
||||||
|
register :block
|
||||||
|
|
||||||
|
# Test if evaluator is transitive
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
# if block is transitive
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def transitive?
|
||||||
|
body.all?(&:transitive?)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Call transformer
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
body.reduce(input) do |state, evaluator|
|
||||||
|
evaluator.call(state)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
self.class.new(body.reverse.map(&:inverse))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return evaluation for input
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Evaluation::Nary]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
# rubocop:disable MethodLength
|
||||||
|
#
|
||||||
|
def evaluation(input)
|
||||||
|
state = input
|
||||||
|
|
||||||
|
evaluations = body.each_with_object([]) do |evaluator, aggregate|
|
||||||
|
evaluation = evaluator.evaluation(state)
|
||||||
|
aggregate << evaluation
|
||||||
|
unless evaluation.success?
|
||||||
|
return evaluation_error(input, aggregate)
|
||||||
|
end
|
||||||
|
state = evaluation.output
|
||||||
|
end
|
||||||
|
|
||||||
|
Evaluation::Nary.success(
|
||||||
|
evaluator: self,
|
||||||
|
input: input,
|
||||||
|
output: state,
|
||||||
|
evaluations: evaluations
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end # Block
|
||||||
|
end # Transformer
|
||||||
|
end # Evaluato
|
||||||
|
end # Morpher
|
166
lib/morpher/evaluator/transformer/coerce.rb
Normal file
166
lib/morpher/evaluator/transformer/coerce.rb
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Transformer
|
||||||
|
# Abstract namespace class for coercing transformers
|
||||||
|
class Coerce < self
|
||||||
|
include AbstractType, Nullary::Parameterized, Transitive
|
||||||
|
|
||||||
|
# Parse mixin for cases the parsing possibly results
|
||||||
|
# in Argument and Type errors.
|
||||||
|
module Parse
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
invoke(input)
|
||||||
|
rescue ArgumentError, TypeError
|
||||||
|
raise_transform_error(input)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return evaluation
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Evaluation]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluation(input)
|
||||||
|
evaluation_success(input, invoke(input))
|
||||||
|
rescue ArgumentError, TypeError
|
||||||
|
evaluation_error(input)
|
||||||
|
end
|
||||||
|
end # Parse
|
||||||
|
|
||||||
|
# Evaluator for parsing an integer
|
||||||
|
class ParseInt < self
|
||||||
|
|
||||||
|
include Parse
|
||||||
|
|
||||||
|
register :parse_int
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
IntToString.new(param)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Invoke coercion
|
||||||
|
#
|
||||||
|
# @return [Integer]
|
||||||
|
#
|
||||||
|
# @raise [ArgumentError, TypeError]
|
||||||
|
# if coercion does not succeed
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def invoke(input)
|
||||||
|
Integer(input, param)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # ParseInt
|
||||||
|
|
||||||
|
# Evaluator for dumping fixnums to strings
|
||||||
|
class IntToString < self
|
||||||
|
|
||||||
|
register :int_to_string
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Hash<Symbol, Object>]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
input.to_s(param)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
ParseInt.new(param)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # IntToString
|
||||||
|
|
||||||
|
# Evaluator for parsing an ISO8601 String into a DateTime
|
||||||
|
class ParseIso8601DateTime < self
|
||||||
|
|
||||||
|
include Parse
|
||||||
|
|
||||||
|
register :parse_iso8601_date_time
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
DateTimeToIso8601String.new(param)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Invoke coercion
|
||||||
|
#
|
||||||
|
# @return [DateTime]
|
||||||
|
#
|
||||||
|
# @raise [ArgumentError, TypeError]
|
||||||
|
# if coercion does not succeed
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def invoke(input)
|
||||||
|
DateTime.iso8601(input)
|
||||||
|
end
|
||||||
|
end # ParseIso8601DateTime
|
||||||
|
|
||||||
|
# Evaluator for dumping a DateTime to an ISO8601 string
|
||||||
|
class DateTimeToIso8601String < self
|
||||||
|
register :date_time_to_iso8601_string
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
input.iso8601(param)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
ParseIso8601DateTime.new(param)
|
||||||
|
end
|
||||||
|
end # DateTimeToIso8601String
|
||||||
|
end # Fixnum
|
||||||
|
end # Transformer
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
34
lib/morpher/evaluator/transformer/custom.rb
Normal file
34
lib/morpher/evaluator/transformer/custom.rb
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Transformer < self
|
||||||
|
# Custom transformer with external injected behavior
|
||||||
|
class Custom < self
|
||||||
|
include Nullary::Parameterized, Transitive
|
||||||
|
register :custom
|
||||||
|
|
||||||
|
# Call transformer with input
|
||||||
|
#
|
||||||
|
# @param [Object]
|
||||||
|
#
|
||||||
|
# @return [undefinedo]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
param.first.call(input)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return inverse transformer
|
||||||
|
#
|
||||||
|
# @return [Evaluator::Transformer]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
self.class.new(param.reverse)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Custom
|
||||||
|
end # Transformer
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
86
lib/morpher/evaluator/transformer/domain.rb
Normal file
86
lib/morpher/evaluator/transformer/domain.rb
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Transformer
|
||||||
|
# Abstract namespace class for transformers from/to domain
|
||||||
|
class Domain < self
|
||||||
|
include AbstractType, Nullary::Parameterized, Transitive
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Call block with param attributes
|
||||||
|
#
|
||||||
|
# @param [Object] aggregate
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def transform(aggregate, &block)
|
||||||
|
param.attributes.each_with_object(aggregate, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Mixin for domain dumpers
|
||||||
|
module Dump
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
self.class::Load.new(param)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Dump object
|
||||||
|
#
|
||||||
|
# @param [Symbol] left
|
||||||
|
# @param [Symbol] right
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def dump(&block)
|
||||||
|
transform({}, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Dump
|
||||||
|
|
||||||
|
# Mixin for domain loaders
|
||||||
|
module Load
|
||||||
|
include AbstractType
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
self.class::Dump.new(param)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Load object
|
||||||
|
#
|
||||||
|
# @param [Symbol] left
|
||||||
|
# @param [Symbol] right
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def load(&block)
|
||||||
|
transform(param.model.allocate, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Load
|
||||||
|
|
||||||
|
end # Domain
|
||||||
|
end # Transformer
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
|
@ -0,0 +1,60 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Transformer
|
||||||
|
class Domain
|
||||||
|
# Abstract namespace class for domain objects via attribute accessors
|
||||||
|
class AttributeAccessors < self
|
||||||
|
include AbstractType
|
||||||
|
|
||||||
|
# Evaluator for dumping domain objects via instance variables
|
||||||
|
class Dump < self
|
||||||
|
include Domain::Dump
|
||||||
|
|
||||||
|
register :dump_attribute_accessors
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Hash<Symbol, Object>]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
dump do |attribute, attributes|
|
||||||
|
name = attribute.name
|
||||||
|
attributes[name] = input.public_send(name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Dump
|
||||||
|
|
||||||
|
# Evaluator for loading domain objects via attributes hash
|
||||||
|
class Load < self
|
||||||
|
include Domain::Load
|
||||||
|
|
||||||
|
register :load_attribute_accessors
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
load do |attribute, object|
|
||||||
|
object.public_send(
|
||||||
|
attribute.writer,
|
||||||
|
input.fetch(attribute.name)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Load
|
||||||
|
end # Domain
|
||||||
|
end # Anima
|
||||||
|
end # Transformer
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
52
lib/morpher/evaluator/transformer/domain/attribute_hash.rb
Normal file
52
lib/morpher/evaluator/transformer/domain/attribute_hash.rb
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Transformer
|
||||||
|
class Domain < self
|
||||||
|
# Abstract namespace class for domain objects on attributes hash
|
||||||
|
class AttributeHash < self
|
||||||
|
include AbstractType
|
||||||
|
|
||||||
|
# Evaluator for dumping domain objects via attributes hash
|
||||||
|
class Dump < self
|
||||||
|
include Domain::Dump
|
||||||
|
|
||||||
|
register :dump_attribute_hash
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Hash<Symbol, Object>]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
input.to_h
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Dump
|
||||||
|
|
||||||
|
# Evaluator for loading domain objects via attributes hash
|
||||||
|
class Load < self
|
||||||
|
include Domain::Load
|
||||||
|
|
||||||
|
register :load_attribute_hash
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
param.model.new(input)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Load
|
||||||
|
end # Domain
|
||||||
|
end # Anima
|
||||||
|
end # Transformer
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
|
@ -0,0 +1,60 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Transformer
|
||||||
|
class Domain < self
|
||||||
|
# Abstract namespace class for domain objects via instance variables
|
||||||
|
class InstanceVariables < self
|
||||||
|
include AbstractType
|
||||||
|
|
||||||
|
# Evaluator for dumping domain objects via instance variables
|
||||||
|
class Dump < self
|
||||||
|
include Domain::Dump
|
||||||
|
|
||||||
|
register :dump_instance_variables
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Hash<Symbol, Object>]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
dump do |attribute, attributes|
|
||||||
|
attributes[attribute.name] =
|
||||||
|
input.instance_variable_get(attribute.ivar_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Dump
|
||||||
|
|
||||||
|
# Evaluator for loading domain objects via instance variables
|
||||||
|
class Load < self
|
||||||
|
include Domain::Load
|
||||||
|
|
||||||
|
register :load_instance_variables
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
load do |attribute, object|
|
||||||
|
object.instance_variable_set(
|
||||||
|
attribute.ivar_name,
|
||||||
|
input.fetch(attribute.name)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Load
|
||||||
|
end # Domain
|
||||||
|
end # Anima
|
||||||
|
end # Transformer
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
54
lib/morpher/evaluator/transformer/domain/param.rb
Normal file
54
lib/morpher/evaluator/transformer/domain/param.rb
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Transformer
|
||||||
|
class Domain < self
|
||||||
|
|
||||||
|
# Domain specific transformer parameter
|
||||||
|
class Param
|
||||||
|
include Adamantium, Concord::Public.new(:model, :attribute_names)
|
||||||
|
|
||||||
|
# Return attributes
|
||||||
|
#
|
||||||
|
# @return [Enumerable<Attribute>]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def attributes
|
||||||
|
attribute_names.map(&Attribute.method(:new))
|
||||||
|
end
|
||||||
|
memoize :attributes
|
||||||
|
|
||||||
|
# Attribute on a domain transformer param
|
||||||
|
class Attribute
|
||||||
|
include Adamantium, Concord::Public.new(:name)
|
||||||
|
|
||||||
|
# Return instance variable name
|
||||||
|
#
|
||||||
|
# @return [Symbol]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def ivar_name
|
||||||
|
:"@#{name}"
|
||||||
|
end
|
||||||
|
memoize :ivar_name
|
||||||
|
|
||||||
|
# Return writer name
|
||||||
|
#
|
||||||
|
# @return [Symbol]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def writer
|
||||||
|
:"#{name}="
|
||||||
|
end
|
||||||
|
memoize :writer
|
||||||
|
|
||||||
|
end # Attribute
|
||||||
|
|
||||||
|
end # Param
|
||||||
|
|
||||||
|
end # Domain
|
||||||
|
end # Transformer
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
62
lib/morpher/evaluator/transformer/guard.rb
Normal file
62
lib/morpher/evaluator/transformer/guard.rb
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Transformer
|
||||||
|
|
||||||
|
# Transformer that allows to guard transformation process
|
||||||
|
# with a predicate on input
|
||||||
|
class Guard < self
|
||||||
|
include Unary, Transitive
|
||||||
|
|
||||||
|
register :guard
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
# if input evaluates true under predicate
|
||||||
|
#
|
||||||
|
# @raise [TransformError]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
if operand.call(input)
|
||||||
|
input
|
||||||
|
else
|
||||||
|
fail TransformError.new(self, input)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return evaluation
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Evaluation::Guard]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluation(input)
|
||||||
|
operand_evaluation = operand.evaluation(input)
|
||||||
|
if operand_evaluation.output
|
||||||
|
evaluation_success(input, operand_evaluation, input)
|
||||||
|
else
|
||||||
|
evaluation_error(input, operand_evaluation)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [self]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Guard
|
||||||
|
end # Transformer
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
149
lib/morpher/evaluator/transformer/hash_transform.rb
Normal file
149
lib/morpher/evaluator/transformer/hash_transform.rb
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Transformer
|
||||||
|
|
||||||
|
# Too complex hash transformation evaluator
|
||||||
|
#
|
||||||
|
# FIXME: Should be broken up in better
|
||||||
|
# primitives a decompose, compose pair
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
class HashTransform < self
|
||||||
|
include Nary
|
||||||
|
|
||||||
|
register :hash_transform
|
||||||
|
|
||||||
|
# Test if evaluator is transitive
|
||||||
|
#
|
||||||
|
# FIXME: Needs to be calculated dynamically
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
# if evaluator is transitive
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def transitive?
|
||||||
|
body.all?(&self.class.method(:transitive_keypair?))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test if evaluator is a keypair
|
||||||
|
#
|
||||||
|
# FIXME: Refactor the need for this away.
|
||||||
|
#
|
||||||
|
# This is a side effect from this class is
|
||||||
|
# generally to big in sense of SRP.
|
||||||
|
# Must be refactorable away. But dunno now.
|
||||||
|
# Still exploring.
|
||||||
|
#
|
||||||
|
# @param [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.transitive_keypair?(evaluator)
|
||||||
|
return false unless evaluator.kind_of?(Block)
|
||||||
|
|
||||||
|
body = evaluator.body
|
||||||
|
|
||||||
|
left, operator, right = body if body.length.equal?(3)
|
||||||
|
|
||||||
|
left.kind_of?(Key::Fetch) &&
|
||||||
|
right.kind_of?(Key::Dump) &&
|
||||||
|
operator.transitive?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
content = body.map do |node|
|
||||||
|
node.call(input)
|
||||||
|
end
|
||||||
|
Hash[content]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [HashTransform]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
self.class.new(body.map(&:inverse))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return evaluation
|
||||||
|
#
|
||||||
|
# @param [Input]
|
||||||
|
#
|
||||||
|
# @return [Evaluation::Nary]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
# rubocop:disable MethodLength
|
||||||
|
#
|
||||||
|
def evaluation(input)
|
||||||
|
evaluations = body.each_with_object([]) do |evaluator, aggregate|
|
||||||
|
evaluation = evaluator.evaluation(input)
|
||||||
|
aggregate << evaluation
|
||||||
|
unless evaluation.success?
|
||||||
|
return evaluation_error(input, aggregate)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
output = Hash[evaluations.map(&:output)]
|
||||||
|
|
||||||
|
Evaluation::Nary.success(
|
||||||
|
evaluator: self,
|
||||||
|
input: input,
|
||||||
|
evaluations: evaluations,
|
||||||
|
output: output
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return evaluation error
|
||||||
|
#
|
||||||
|
# @return [Object] input
|
||||||
|
#
|
||||||
|
# @return [Array<Evaluations>] evaluations
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluation_error(input, evaluations)
|
||||||
|
Evaluation::Nary.error(
|
||||||
|
evaluator: self,
|
||||||
|
input: input,
|
||||||
|
evaluations: evaluations
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Build evaluator from node
|
||||||
|
#
|
||||||
|
# @param [Compiler] compiler
|
||||||
|
# @param [Node] node
|
||||||
|
#
|
||||||
|
# @return [Evaluator]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.build(compiler, node)
|
||||||
|
body = node.children.map do |child|
|
||||||
|
compiler.call(child)
|
||||||
|
end
|
||||||
|
new(body)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # HashTransform
|
||||||
|
end # Transformer
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
37
lib/morpher/evaluator/transformer/input.rb
Normal file
37
lib/morpher/evaluator/transformer/input.rb
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Transformer
|
||||||
|
|
||||||
|
# Identity transformer which always returns +input+
|
||||||
|
class Input < self
|
||||||
|
include Nullary, Transitive
|
||||||
|
|
||||||
|
register :input
|
||||||
|
|
||||||
|
# Call evaluator with input
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
# always returns input
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
input
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator::Transformer]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Input
|
||||||
|
end # Transformer
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
86
lib/morpher/evaluator/transformer/key.rb
Normal file
86
lib/morpher/evaluator/transformer/key.rb
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Transformer
|
||||||
|
# Abstract namespace class for evaluators operating on hash keys
|
||||||
|
class Key < self
|
||||||
|
include AbstractType, Nullary::Parameterized, Intransitive
|
||||||
|
|
||||||
|
# Evaluator for dumping hash keys
|
||||||
|
class Dump < self
|
||||||
|
|
||||||
|
register :key_dump
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Object] object
|
||||||
|
#
|
||||||
|
# @return [Array]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(object)
|
||||||
|
[param, object]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [Fetch]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
Fetch.new(param)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Dump
|
||||||
|
|
||||||
|
# Evaluator to fetch a specific hash key
|
||||||
|
class Fetch < self
|
||||||
|
|
||||||
|
register :key_fetch
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Hash] object
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(object)
|
||||||
|
object.fetch(param) do
|
||||||
|
fail TransformError.new(self, object)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return evaluation
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Evaluation]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluation(input)
|
||||||
|
output = input.fetch(param) do
|
||||||
|
return evaluation_error(input)
|
||||||
|
end
|
||||||
|
|
||||||
|
evaluation_success(input, output)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [Dump]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
Dump.new(param)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Fetch
|
||||||
|
end # Key
|
||||||
|
end # Transformer
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
100
lib/morpher/evaluator/transformer/map.rb
Normal file
100
lib/morpher/evaluator/transformer/map.rb
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Transformer
|
||||||
|
|
||||||
|
# Transformer over each element in an enumerable
|
||||||
|
class Map < self
|
||||||
|
include Unary
|
||||||
|
|
||||||
|
register :map
|
||||||
|
|
||||||
|
# Test if evaluator is transitive
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
# if evaluator is transitive
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def transitive?
|
||||||
|
operand.transitive?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Call evaluator
|
||||||
|
#
|
||||||
|
# @param [Enumerable#map] input
|
||||||
|
#
|
||||||
|
# @return [Enumerable]
|
||||||
|
# if input evaluates true under predicate
|
||||||
|
#
|
||||||
|
# @raise [TransformError]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
input.map(&operand.method(:call))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return evaluation
|
||||||
|
#
|
||||||
|
# @param [Enumerable#map] input
|
||||||
|
#
|
||||||
|
# @return [Evaluation]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
# rubocop:disable MethodLength
|
||||||
|
#
|
||||||
|
def evaluation(input)
|
||||||
|
evaluations = input.each_with_object([]) do |item, aggregate|
|
||||||
|
evaluation = operand.evaluation(item)
|
||||||
|
aggregate << evaluation
|
||||||
|
unless evaluation.success?
|
||||||
|
return evaluation_error(input, aggregate)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Evaluation::Nary.success(
|
||||||
|
evaluator: self,
|
||||||
|
input: input,
|
||||||
|
output: evaluations.map(&:output),
|
||||||
|
evaluations: evaluations
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return inverse evaluator
|
||||||
|
#
|
||||||
|
# @return [Evaluator::Transformer]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def inverse
|
||||||
|
self.class.new(operand.inverse)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return evaluation error
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
# @param [Array<Evaluation>] evaluations
|
||||||
|
#
|
||||||
|
# @return [Evaluation]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluation_error(input, evaluations)
|
||||||
|
Evaluation::Nary.error(
|
||||||
|
evaluator: self,
|
||||||
|
input: input,
|
||||||
|
evaluations: evaluations
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Guard
|
||||||
|
end # Transformer
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
25
lib/morpher/evaluator/transformer/merge.rb
Normal file
25
lib/morpher/evaluator/transformer/merge.rb
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Transformer
|
||||||
|
# Transformer to merge input into defaults
|
||||||
|
class Merge < self
|
||||||
|
include Intransitive, Nullary::Parameterized
|
||||||
|
|
||||||
|
register :merge
|
||||||
|
|
||||||
|
# Call evaluator for input
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Object] output
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(input)
|
||||||
|
param.merge(input)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Merge
|
||||||
|
end # Transformer
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
27
lib/morpher/evaluator/transformer/static.rb
Normal file
27
lib/morpher/evaluator/transformer/static.rb
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
class Transformer
|
||||||
|
|
||||||
|
# Transformer that always returns the passed +param+
|
||||||
|
class Static < self
|
||||||
|
include Nullary::Parameterized, Intransitive
|
||||||
|
|
||||||
|
register :static
|
||||||
|
|
||||||
|
# Call evaluator with input
|
||||||
|
#
|
||||||
|
# @param [Object] _input
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
# alwasys returns the param
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(_input)
|
||||||
|
param
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Static
|
||||||
|
end # Transformer
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
79
lib/morpher/evaluator/unary.rb
Normal file
79
lib/morpher/evaluator/unary.rb
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
module Morpher
|
||||||
|
class Evaluator
|
||||||
|
|
||||||
|
# Mixin for unary evaluators
|
||||||
|
module Unary
|
||||||
|
CONCORD = Concord::Public.new(:operand)
|
||||||
|
|
||||||
|
PRINTER = lambda do |_|
|
||||||
|
name
|
||||||
|
indent do
|
||||||
|
visit(:operand)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return node
|
||||||
|
#
|
||||||
|
# @return [AST::Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def node
|
||||||
|
s(type, operand.node)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return success evaluation for input
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Evalation::Unary]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluation_success(input, operand_evaluation, output)
|
||||||
|
Evaluation::Unary.success(
|
||||||
|
evaluator: self,
|
||||||
|
input: input,
|
||||||
|
operand_evaluation: operand_evaluation,
|
||||||
|
output: output
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return error evaluation for input
|
||||||
|
#
|
||||||
|
# @param [Object] input
|
||||||
|
#
|
||||||
|
# @return [Evalation::Unary]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def evaluation_error(input, operand_evaluation)
|
||||||
|
Evaluation::Unary.error(
|
||||||
|
evaluator: self,
|
||||||
|
input: input,
|
||||||
|
operand_evaluation: operand_evaluation
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Hook called when module gets included
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.included(descendant)
|
||||||
|
descendant.class_eval do
|
||||||
|
include CONCORD
|
||||||
|
printer(&PRINTER)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :included
|
||||||
|
|
||||||
|
end # Unary
|
||||||
|
|
||||||
|
end # Evaluator
|
||||||
|
end # Morpher
|
19
lib/morpher/node_helpers.rb
Normal file
19
lib/morpher/node_helpers.rb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
module Morpher
|
||||||
|
# Node helpers
|
||||||
|
module NodeHelpers
|
||||||
|
|
||||||
|
# Build node
|
||||||
|
#
|
||||||
|
# @param [Symbol] type
|
||||||
|
#
|
||||||
|
# @return [Parser::AST::Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def s(type, *children)
|
||||||
|
AST::Node.new(type, children)
|
||||||
|
end
|
||||||
|
module_function :s
|
||||||
|
|
||||||
|
end # NodeHelpers
|
||||||
|
end # Morpher
|
233
lib/morpher/printer.rb
Normal file
233
lib/morpher/printer.rb
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
module Morpher
|
||||||
|
|
||||||
|
# Evaluation and Evaluator pretty printer
|
||||||
|
class Printer
|
||||||
|
include Adamantium::Flat, Concord.new(:object, :output, :indent_level)
|
||||||
|
|
||||||
|
INDENT = ' '.freeze
|
||||||
|
|
||||||
|
REGISTRY = {}
|
||||||
|
|
||||||
|
# Run pretty printer on object
|
||||||
|
#
|
||||||
|
# @param [Object] object
|
||||||
|
# the object to be pretty printed
|
||||||
|
# @param [IO] output
|
||||||
|
# the output to write to
|
||||||
|
# @param [Fixnum] indent_level
|
||||||
|
# the current indentation level
|
||||||
|
#
|
||||||
|
# @return [self]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.run(object, output, indent_level = 0)
|
||||||
|
printer = new(object, output, indent_level)
|
||||||
|
block = lookup(object)
|
||||||
|
printer.instance_eval(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Perform type lookup
|
||||||
|
#
|
||||||
|
# FIXME: Instanciate type lookup once and allow caching.
|
||||||
|
#
|
||||||
|
# @param [Evaluation, Evaluator] object
|
||||||
|
#
|
||||||
|
# @return [Proc]
|
||||||
|
# if found
|
||||||
|
#
|
||||||
|
# @raise [PrinterMissingException]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.lookup(object)
|
||||||
|
TypeLookup.new(REGISTRY).call(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Visit a child
|
||||||
|
#
|
||||||
|
# @param [Node] child
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
def visit_child(child)
|
||||||
|
self.class.run(child, output, indent_level.succ)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Visit a child by name
|
||||||
|
#
|
||||||
|
# @param [Symbol] name
|
||||||
|
# the attribute name of the child to visit
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def visit(name)
|
||||||
|
child = object.public_send(name)
|
||||||
|
child_label(name)
|
||||||
|
visit_child(child)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Visit many children
|
||||||
|
#
|
||||||
|
# @param [Symbol] name
|
||||||
|
# the name of the collection attribute with children to visit
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def visit_many(name)
|
||||||
|
children = object.public_send(name)
|
||||||
|
child_label(name)
|
||||||
|
children.each do |child|
|
||||||
|
visit_child(child)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Print attribute class
|
||||||
|
#
|
||||||
|
# @param [Symbol] name
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def attribute_class(name)
|
||||||
|
label_value(name, object.public_send(name).class)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Print inspected attribute value with label
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def attribute(name)
|
||||||
|
label_value(name, object.public_send(name))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Print attributes of object
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def attributes(*names)
|
||||||
|
names.each do |name|
|
||||||
|
attribute(name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Print name of object
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def name
|
||||||
|
puts(object.class.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return string indented with current level
|
||||||
|
#
|
||||||
|
# @param [String] content
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def indented(content)
|
||||||
|
"#{indentation_prefix}#{content}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return indentation prefix
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def indentation_prefix
|
||||||
|
INDENT * indent_level
|
||||||
|
end
|
||||||
|
memoize :indentation_prefix
|
||||||
|
|
||||||
|
# Add content to output at current indentation and close line
|
||||||
|
#
|
||||||
|
# @param [String] content
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def puts(string)
|
||||||
|
output.puts(indented(string))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Write content to output at current indentation
|
||||||
|
#
|
||||||
|
# @param [String] content
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def write(string)
|
||||||
|
output.write(indented(string))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Write child label to output at current indentation
|
||||||
|
#
|
||||||
|
# @param [String] label
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def child_label(string)
|
||||||
|
puts("#{string}:")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Call block inside indented context
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def indent(&block)
|
||||||
|
printer = new(object, output, indent_level.succ)
|
||||||
|
printer.instance_eval(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return new printer
|
||||||
|
#
|
||||||
|
# @return [Printer]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def new(*arguments)
|
||||||
|
self.class.new(*arguments)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Print label with value
|
||||||
|
#
|
||||||
|
# @param [String] label
|
||||||
|
# @param [Object] value
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def label_value(label, value)
|
||||||
|
write("#{label}: ")
|
||||||
|
output.puts(value.inspect)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Printer
|
||||||
|
end # Morpher
|
58
lib/morpher/printer/mixin.rb
Normal file
58
lib/morpher/printer/mixin.rb
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
module Morpher
|
||||||
|
class Printer
|
||||||
|
|
||||||
|
# Printer behavior mixin
|
||||||
|
module Mixin
|
||||||
|
|
||||||
|
# Class level methods to be mixed in
|
||||||
|
module ClassMethods
|
||||||
|
|
||||||
|
# Register printer block for class
|
||||||
|
#
|
||||||
|
# @return [self]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def printer(&block)
|
||||||
|
REGISTRY[self] = block
|
||||||
|
end
|
||||||
|
|
||||||
|
end # ClassMethods
|
||||||
|
|
||||||
|
# Instance level methods to be mixed in
|
||||||
|
module InstanceMethods
|
||||||
|
|
||||||
|
# Return description
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def description
|
||||||
|
io = StringIO.new
|
||||||
|
Printer.run(self, io)
|
||||||
|
io.rewind
|
||||||
|
io.read
|
||||||
|
end
|
||||||
|
|
||||||
|
end # InstanceMethods
|
||||||
|
|
||||||
|
# Callback whem module gets included
|
||||||
|
#
|
||||||
|
# @param [Class, Module] descendant
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.included(descendant)
|
||||||
|
descendant.class_eval do
|
||||||
|
extend ClassMethods
|
||||||
|
include InstanceMethods
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :included
|
||||||
|
|
||||||
|
end # Mixin
|
||||||
|
end # Printer
|
||||||
|
end # Morpher
|
51
lib/morpher/registry.rb
Normal file
51
lib/morpher/registry.rb
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
module Morpher
|
||||||
|
# Mixin for to provide a node registry
|
||||||
|
module Registry
|
||||||
|
|
||||||
|
# Hook called when module is included
|
||||||
|
#
|
||||||
|
# @param [Module, Class] descendant
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.included(descendant)
|
||||||
|
descendant.const_set(:REGISTRY, {})
|
||||||
|
descendant.class_eval do
|
||||||
|
extend ClassMethods
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return node type
|
||||||
|
#
|
||||||
|
# @return [Symbol]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def type
|
||||||
|
self.class::TYPE
|
||||||
|
end
|
||||||
|
|
||||||
|
# Methods to get mixed in at singleton level
|
||||||
|
module ClassMethods
|
||||||
|
|
||||||
|
# Register evaluator under name
|
||||||
|
#
|
||||||
|
# TODO: Disallow duplicate registration under same name
|
||||||
|
#
|
||||||
|
# @param [Symbol] name
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def register(name)
|
||||||
|
const_set(:TYPE, name)
|
||||||
|
self::REGISTRY[name] = self
|
||||||
|
end
|
||||||
|
|
||||||
|
end # ClassMethods
|
||||||
|
|
||||||
|
end # Registry
|
||||||
|
end # Morpher
|
51
lib/morpher/type_lookup.rb
Normal file
51
lib/morpher/type_lookup.rb
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
module Morpher
|
||||||
|
|
||||||
|
# Type lookup via registry and superclass chaining
|
||||||
|
#
|
||||||
|
# TODO: Cache results.
|
||||||
|
#
|
||||||
|
class TypeLookup
|
||||||
|
include Adamantium::Flat, Concord.new(:registry)
|
||||||
|
|
||||||
|
# Error raised on compiling unknown nodes
|
||||||
|
class TypeNotFoundError < RuntimeError
|
||||||
|
include Concord.new(:type)
|
||||||
|
|
||||||
|
# Return exception error message
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def message
|
||||||
|
"Node type: #{type.inspect} is unknown"
|
||||||
|
end
|
||||||
|
|
||||||
|
end # TypeNotFoundError
|
||||||
|
|
||||||
|
# Perform type lookup
|
||||||
|
#
|
||||||
|
# @param [Object] object
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
# if found
|
||||||
|
#
|
||||||
|
# @raise [TypeNotFoundError]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(object)
|
||||||
|
current = target = object.class
|
||||||
|
while current != Object
|
||||||
|
if registry.key?(current)
|
||||||
|
return registry.fetch(current)
|
||||||
|
end
|
||||||
|
current = current.superclass
|
||||||
|
end
|
||||||
|
|
||||||
|
fail TypeNotFoundError, target
|
||||||
|
end
|
||||||
|
|
||||||
|
end # TypeLookup
|
||||||
|
end # Morpher
|
|
@ -30,7 +30,6 @@ Gem::Specification.new do |gem|
|
||||||
gem.add_runtime_dependency('equalizer', '~> 0.0.9')
|
gem.add_runtime_dependency('equalizer', '~> 0.0.9')
|
||||||
gem.add_runtime_dependency('ice_nine', '~> 0.11.1')
|
gem.add_runtime_dependency('ice_nine', '~> 0.11.1')
|
||||||
gem.add_runtime_dependency('memoizable', '~> 0.4.2')
|
gem.add_runtime_dependency('memoizable', '~> 0.4.2')
|
||||||
gem.add_runtime_dependency('morpher', '~> 0.3.1')
|
|
||||||
gem.add_runtime_dependency('parser', '~> 2.7.2')
|
gem.add_runtime_dependency('parser', '~> 2.7.2')
|
||||||
gem.add_runtime_dependency('procto', '~> 0.0.2')
|
gem.add_runtime_dependency('procto', '~> 0.0.2')
|
||||||
gem.add_runtime_dependency('regexp_parser', '~> 1.8.2')
|
gem.add_runtime_dependency('regexp_parser', '~> 1.8.2')
|
||||||
|
|
Loading…
Reference in a new issue