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)
|
||||
ice_nine (~> 0.11.1)
|
||||
memoizable (~> 0.4.2)
|
||||
morpher (~> 0.3.1)
|
||||
parser (~> 2.7.2)
|
||||
procto (~> 0.0.2)
|
||||
regexp_parser (~> 1.8.2)
|
||||
|
@ -87,23 +86,6 @@ GEM
|
|||
kwalify (0.7.2)
|
||||
memoizable (0.4.2)
|
||||
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)
|
||||
parser (2.7.2.0)
|
||||
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('ice_nine', '~> 0.11.1')
|
||||
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('procto', '~> 0.0.2')
|
||||
gem.add_runtime_dependency('regexp_parser', '~> 1.8.2')
|
||||
|
|
Loading…
Reference in a new issue