Class: Puppet::Pops::Evaluator::RelationshipOperator

Inherits:
Object
  • Object
show all
Includes:
Runtime3Support
Defined in:
lib/puppet/pops/evaluator/relationship_operator.rb

Overview

The RelationshipOperator implements the semantics of the -> <- ~> <~ operators creating relationships or notification relationships between the left and right hand side's references to resources.

This is separate class since a second level of evaluation is required that transforms string in left or right hand to type references. The task of “making a relationship” is delegated to the “runtime support” class that is included. This is done to separate the concerns of the new evaluator from the 3x runtime; messy logic goes into the runtime support module. Later when more is cleaned up this can be simplified further.

Defined Under Namespace

Classes: IllegalRelationshipOperandError, NotCatalogTypeError

Constant Summary collapse

RELATIONSHIP_OPERATORS =
['->', '~>', '<-', '<~'].freeze
REVERSE_OPERATORS =
['<-', '<~'].freeze
RELATION_TYPE =
{
  '->' => :relationship,
  '<-' => :relationship,
  '~>' => :subscription,
  '<~' => :subscription
}.freeze

Constants included from Runtime3Support

Puppet::Pops::Evaluator::Runtime3Support::NAME_SPACE_SEPARATOR

Instance Method Summary collapse

Methods included from Runtime3Support

#add_relationship, #call_function, #capitalize_qualified_name, #coerce_numeric, #convert, #create_local_scope_from, #create_match_scope_from, #create_resource_defaults, #create_resource_overrides, #create_resource_parameter, #create_resources, #diagnostic_producer, #external_call_function, #extract_file_line, #fail, #find_resource, #get_resource_parameter_value, #get_scope_nesting_level, #get_variable_value, #is_boolean?, #is_parameter_of_resource?, #is_true?, #optionally_fail, #resource_to_ptype, #runtime_issue, #set_match_data, #set_scope_nesting_level, #set_variable, #variable_bound?, #variable_exists?

Constructor Details

#initializeRelationshipOperator

Returns a new instance of RelationshipOperator


32
33
34
35
36
37
38
# File 'lib/puppet/pops/evaluator/relationship_operator.rb', line 32

def initialize
  @type_transformer_visitor = Visitor.new(self, "transform", 1, 1)
  @type_calculator = Types::TypeCalculator.new()

  tf = Types::TypeFactory
  @catalog_type = tf.variant(tf.catalog_entry, tf.type_type(tf.catalog_entry))
end

Instance Method Details

#assert_catalog_type(o, scope) ⇒ Object

Asserts (and returns) the type if it is a PCatalogEntryType (A PCatalogEntryType is the base class of PClassType, and PResourceType).


96
97
98
99
100
101
102
103
104
# File 'lib/puppet/pops/evaluator/relationship_operator.rb', line 96

def assert_catalog_type(o, scope)
  unless @type_calculator.assignable?(@catalog_type, o)
    raise NotCatalogTypeError.new(o)
  end
  # TODO must check if this is an abstract PResourceType (i.e. without a type_name) - which should fail ?
  # e.g. File -> File (and other similar constructs) - maybe the catalog protects against this since references
  # may be to future objects...
  o
end

#evaluate(left_right_evaluated, relationship_expression, scope) ⇒ Object

Evaluate a relationship. TODO: The error reporting is not fine grained since evaluation has already taken place There is no references to the original source expressions at this point, only the overall relationship expression. (e.g.. the expression may be ['string', func_call(), etc.] -> func_call()) To implement this, the general evaluator needs to be able to track each evaluation result and associate it with a corresponding expression. This structure should then be passed to the relationship operator.


122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/puppet/pops/evaluator/relationship_operator.rb', line 122

def evaluate (left_right_evaluated, relationship_expression, scope)
  # assert operator (should have been validated, but this logic makes assumptions which would
  # screw things up royally). Better safe than sorry.
  unless RELATIONSHIP_OPERATORS.include?(relationship_expression.operator)
    fail(Issues::UNSUPPORTED_OPERATOR, relationship_expression, {:operator => relationship_expression.operator})
  end

  begin
    # Turn each side into an array of types (this also asserts their type)
    # (note wrap in array first if value is not already an array)
    #
    # TODO: Later when objects are Puppet Runtime Objects and know their type, it will be more efficient to check/infer
    # the type first since a chained operation then does not have to visit each element again. This is not meaningful now
    # since inference needs to visit each object each time, and this is what the transformation does anyway).
    #
    # real is [left, right], and both the left and right may be a single value or an array. In each case all content
    # should be flattened, and then transformed to a type. left or right may also be a value that is transformed
    # into an array, and thus the resulting left and right must be flattened individually
    # Once flattened, the operands should be sets (to remove duplicate entries)
    #
    real = left_right_evaluated.collect {|x| [x].flatten.collect {|y| transform(y, scope) }}
    real[0].flatten!
    real[1].flatten!
    real[0].uniq!
    real[1].uniq!

    # reverse order if operator is Right to Left
    source, target = reverse_operator?(relationship_expression) ? real.reverse : real

    # Add the relationships to the catalog
    source.each {|s| target.each {|t| add_relationship(s, t, RELATION_TYPE[relationship_expression.operator], scope) }}

    # The result is the transformed source RHS unless it is empty, in which case the transformed LHS is returned.
    # This closes the gap created by an empty set of references in a chain of relationship
    # such that X -> [ ] -> Y results in  X -> Y.
    #result = real[1].empty? ? real[0] : real[1]
    if real[1].empty?
      # right side empty, simply use the left (whatever it may be)
      result = real[0]
    else
      right = real[1]
      if right.size == 1 && right[0].is_a?(Puppet::Pops::Evaluator::Collectors::AbstractCollector)
        # the collector when evaluated later may result in an empty set, if so, the
        # lazy relationship forming logic needs to have access to the left value.
        adapter = Puppet::Pops::Adapters::EmptyAlternativeAdapter.adapt(right[0])
        adapter.empty_alternative = real[0]
      end
      result = right
    end
    result

  rescue NotCatalogTypeError => e
    fail(Issues::NOT_CATALOG_TYPE, relationship_expression, {:type => @type_calculator.string(e.type)})
  rescue IllegalRelationshipOperandError => e
    fail(Issues::ILLEGAL_RELATIONSHIP_OPERAND_TYPE, relationship_expression, {:operand => e.operand})
  end
end

#reverse_operator?(o) ⇒ Boolean


180
181
182
# File 'lib/puppet/pops/evaluator/relationship_operator.rb', line 180

def reverse_operator?(o)
  REVERSE_OPERATORS.include?(o.operator)
end

#transform(o, scope) ⇒ Object


40
41
42
# File 'lib/puppet/pops/evaluator/relationship_operator.rb', line 40

def transform(o, scope)
  @type_transformer_visitor.visit_this_1(self, o, scope)
end

#transform_AbstractCollector(o, scope) ⇒ Object


84
85
86
# File 'lib/puppet/pops/evaluator/relationship_operator.rb', line 84

def transform_AbstractCollector(o, scope)
  o
end

#transform_Array(o, scope) ⇒ Object

Array content needs to be transformed


89
90
91
# File 'lib/puppet/pops/evaluator/relationship_operator.rb', line 89

def transform_Array(o, scope)
  o.map{|x| transform(x, scope) }
end

#transform_Collector(o, scope) ⇒ Object

This transforms a 3x Collector (the result of evaluating a 3x AST::Collection). It is passed through verbatim since it is evaluated late by the compiler. At the point where the relationship is evaluated, it is simply recorded with the compiler for later evaluation. If one of the sides of the relationship is a Collector it is evaluated before the actual relationship is formed. (All of this happens at a later point in time.


80
81
82
# File 'lib/puppet/pops/evaluator/relationship_operator.rb', line 80

def transform_Collector(o, scope)
  o
end

#transform_Object(o, scope) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Catch all non transformable objects


46
47
48
# File 'lib/puppet/pops/evaluator/relationship_operator.rb', line 46

def transform_Object(o, scope)
  raise IllegalRelationshipOperandError.new(o)
end

#transform_PAnyType(o, scope) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Types are what they are, just check the type


70
71
72
# File 'lib/puppet/pops/evaluator/relationship_operator.rb', line 70

def transform_PAnyType(o, scope)
  assert_catalog_type(o, scope)
end

#transform_QualifiedName(o, scope) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

A qualified name is short hand for a class with this name


64
65
66
# File 'lib/puppet/pops/evaluator/relationship_operator.rb', line 64

def transform_QualifiedName(o, scope)
  Types::TypeFactory.host_class(o.value)
end

#transform_Resource(o, scope) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

A Resource is by definition a Catalog type, but of 3.x type


52
53
54
# File 'lib/puppet/pops/evaluator/relationship_operator.rb', line 52

def transform_Resource(o, scope)
  Types::TypeFactory.resource(o.type, o.title)
end

#transform_String(o, scope) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

A string must be a type reference in string format


58
59
60
# File 'lib/puppet/pops/evaluator/relationship_operator.rb', line 58

def transform_String(o, scope)
  assert_catalog_type(Types::TypeParser.singleton.parse(o), scope)
end