Include gem "morpher"

This commit is contained in:
Alex Kotov 2021-09-16 13:35:43 +05:00
parent cf46fc693a
commit 12b8efc801
Signed by: kotovalexarian
GPG key ID: 553C0EBBEB5D5F08
51 changed files with 3378 additions and 19 deletions

View file

@ -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
View 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
View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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
View 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
View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View file

@ -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

View 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

View file

@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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
View 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

View 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
View 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

View 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

View file

@ -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')