2
# The Puppet Pops Metamodel
4
# This module contains a formal description of the Puppet Pops (*P*uppet *OP*eration instruction*S*).
5
# It describes a Metamodel containing DSL instructions, a description of PuppetType and related
6
# classes needed to evaluate puppet logic.
7
# The metamodel resembles the existing AST model, but it is a semantic model of instructions and
8
# the types that they operate on rather than an Abstract Syntax Tree, although closely related.
10
# The metamodel is anemic (has no behavior) except basic datatype and type
11
# assertions and reference/containment assertions.
12
# The metamodel is also a generalized description of the Puppet DSL to enable the
13
# same metamodel to be used to express Puppet DSL models (instances) with different semantics as
14
# the language evolves.
16
# The metamodel is concretized by a validator for a particular version of
17
# the Puppet DSL language.
19
# This metamodel is expressed using RGen.
2
# The Puppet Pops Metamodel Implementation
4
# The Puppet Pops Metamodel consists of two parts; the metamodel expressed with RGen in model_meta.rb,
5
# and this file which mixes in implementation details.
22
8
require 'rgen/metamodel_builder'
24
module Puppet::Pops::Model
25
extend RGen::MetamodelBuilder::ModuleExtension
27
# A base class for modeled objects that makes them Visitable, and Adaptable.
29
class PopsObject < RGen::MetamodelBuilder::MMBase
30
include Puppet::Pops::Visitable
31
include Puppet::Pops::Adaptable
32
include Puppet::Pops::Containment
36
# A Positioned object has an offset measured in an opaque unit (representing characters) from the start
37
# of a source text (starting
38
# from 0), and a length measured in the same opaque unit. The resolution of the opaque unit requires the
39
# aid of a Locator instance that knows about the measure. This information is stored in the model's
40
# root node - a Program.
42
# The offset and length are optional if the source of the model is not from parsed text.
44
class Positioned < PopsObject
46
has_attr 'offset', Integer
47
has_attr 'length', Integer
50
# @abstract base class for expressions
51
class Expression < Positioned
55
# A Nop - the "no op" expression.
56
# @note not really needed since the evaluator can evaluate nil with the meaning of NoOp
57
# @todo deprecate? May be useful if there is the need to differentiate between nil and Nop when transforming model.
59
class Nop < Expression
62
# A binary expression is abstract and has a left and a right expression. The order of evaluation
63
# and semantics are determined by the concrete subclass.
65
class BinaryExpression < Expression
68
# @!attribute [rw] left_expr
69
# @return [Expression]
70
contains_one_uni 'left_expr', Expression, :lowerBound => 1
71
contains_one_uni 'right_expr', Expression, :lowerBound => 1
74
# An unary expression is abstract and contains one expression. The semantics are determined by
75
# a concrete subclass.
77
class UnaryExpression < Expression
79
contains_one_uni 'expr', Expression, :lowerBound => 1
82
# A class that simply evaluates to the contained expression.
83
# It is of value in order to preserve user entered parentheses in transformations, and
84
# transformations from model to source.
86
class ParenthesizedExpression < UnaryExpression; end
88
# A boolean not expression, reversing the truth of the unary expr.
90
class NotExpression < UnaryExpression; end
92
# An arithmetic expression reversing the polarity of the numeric unary expr.
94
class UnaryMinusExpression < UnaryExpression; end
96
# An assignment expression assigns a value to the lval() of the left_expr.
98
class AssignmentExpression < BinaryExpression
99
has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'=', :'+=', :'-=']), :lowerBound => 1
102
# An arithmetic expression applies an arithmetic operator on left and right expressions.
104
class ArithmeticExpression < BinaryExpression
105
has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'+', :'-', :'*', :'%', :'/', :'<<', :'>>' ]), :lowerBound => 1
108
# A relationship expression associates the left and right expressions
110
class RelationshipExpression < BinaryExpression
111
has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'->', :'<-', :'~>', :'<~']), :lowerBound => 1
114
# A binary expression, that accesses the value denoted by right in left. i.e. typically
115
# expressed concretely in a language as left[right].
117
class AccessExpression < Expression
118
contains_one_uni 'left_expr', Expression, :lowerBound => 1
119
contains_many_uni 'keys', Expression, :lowerBound => 1
122
# A comparison expression compares left and right using a comparison operator.
124
class ComparisonExpression < BinaryExpression
125
has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'==', :'!=', :'<', :'>', :'<=', :'>=' ]), :lowerBound => 1
128
# A match expression matches left and right using a matching operator.
130
class MatchExpression < BinaryExpression
131
has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'!~', :'=~']), :lowerBound => 1
134
# An 'in' expression checks if left is 'in' right
136
class InExpression < BinaryExpression; end
138
# A boolean expression applies a logical connective operator (and, or) to left and right expressions.
140
class BooleanExpression < BinaryExpression
144
# An and expression applies the logical connective operator and to left and right expression
145
# and does not evaluate the right expression if the left expression is false.
147
class AndExpression < BooleanExpression; end
149
# An or expression applies the logical connective operator or to the left and right expression
150
# and does not evaluate the right expression if the left expression is true
152
class OrExpression < BooleanExpression; end
154
# A literal list / array containing 0:M expressions.
156
class LiteralList < Expression
157
contains_many_uni 'values', Expression
160
# A Keyed entry has a key and a value expression. It is typically used as an entry in a Hash.
162
class KeyedEntry < Positioned
163
contains_one_uni 'key', Expression, :lowerBound => 1
164
contains_one_uni 'value', Expression, :lowerBound => 1
167
# A literal hash is a collection of KeyedEntry objects
169
class LiteralHash < Expression
170
contains_many_uni 'entries', KeyedEntry
173
# A block contains a list of expressions
175
class BlockExpression < Expression
176
contains_many_uni 'statements', Expression
179
# A case option entry in a CaseStatement
181
class CaseOption < Expression
182
contains_many_uni 'values', Expression, :lowerBound => 1
183
contains_one_uni 'then_expr', Expression, :lowerBound => 1
186
# A case expression has a test, a list of options (multi values => block map).
187
# One CaseOption may contain a LiteralDefault as value. This option will be picked if nothing
190
class CaseExpression < Expression
191
contains_one_uni 'test', Expression, :lowerBound => 1
192
contains_many_uni 'options', CaseOption
195
# A query expression is an expression that is applied to some collection.
196
# The contained optional expression may contain different types of relational expressions depending
197
# on what the query is applied to.
199
class QueryExpression < Expression
201
contains_one_uni 'expr', Expression, :lowerBound => 0
204
# An exported query is a special form of query that searches for exported objects.
206
class ExportedQuery < QueryExpression
209
# A virtual query is a special form of query that searches for virtual objects.
211
class VirtualQuery < QueryExpression
214
# An attribute operation sets or appends a value to a named attribute.
216
class AttributeOperation < Positioned
217
has_attr 'attribute_name', String, :lowerBound => 1
218
has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'=>', :'+>', ]), :lowerBound => 1
219
contains_one_uni 'value_expr', Expression, :lowerBound => 1
222
# An object that collects stored objects from the central cache and returns
223
# them to the current host. Operations may optionally be applied.
225
class CollectExpression < Expression
226
contains_one_uni 'type_expr', Expression, :lowerBound => 1
227
contains_one_uni 'query', QueryExpression, :lowerBound => 1
228
contains_many_uni 'operations', AttributeOperation
231
class Parameter < Positioned
232
has_attr 'name', String, :lowerBound => 1
233
contains_one_uni 'value', Expression
236
# Abstract base class for definitions.
238
class Definition < Expression
242
# Abstract base class for named and parameterized definitions.
243
class NamedDefinition < Definition
245
has_attr 'name', String, :lowerBound => 1
246
contains_many_uni 'parameters', Parameter
247
contains_one_uni 'body', Expression
250
# A resource type definition (a 'define' in the DSL).
252
class ResourceTypeDefinition < NamedDefinition
255
# A node definition matches hosts using Strings, or Regular expressions. It may inherit from
256
# a parent node (also using a String or Regular expression).
258
class NodeDefinition < Definition
259
contains_one_uni 'parent', Expression
260
contains_many_uni 'host_matches', Expression, :lowerBound => 1
261
contains_one_uni 'body', Expression
264
class LocatableExpression < Expression
265
has_many_attr 'line_offsets', Integer
266
has_attr 'locator', Object, :lowerBound => 1, :transient => true
269
# Go through the gymnastics of making either value or pattern settable
270
# with synchronization to the other form. A derived value cannot be serialized
271
# and we want to serialize the pattern. When recreating the object we need to
272
# recreate it from the pattern string.
273
# The below sets both values if one is changed.
276
unless result = getLocator
277
setLocator(result = Puppet::Pops::Parser::Locator.locator(source_text, source_ref(), line_offsets))
284
# Contains one expression which has offsets reported virtually (offset against the Program's
287
class SubLocatedExpression < Expression
288
contains_one_uni 'expr', Expression, :lowerBound => 1
290
# line offset index for contained expressions
291
has_many_attr 'line_offsets', Integer
293
# Number of preceding lines (before the line_offsets)
294
has_attr 'leading_line_count', Integer
296
# The offset of the leading source line (i.e. size of "left margin").
297
has_attr 'leading_line_offset', Integer
299
# The locator for the sub-locatable's children (not for the sublocator itself)
300
# The locator is not serialized and is recreated on demand from the indexing information
303
has_attr 'locator', Object, :lowerBound => 1, :transient => true
307
unless result = getLocator
308
# Adapt myself to get the Locator for me
309
adapter = Puppet::Pops::Adapters::SourcePosAdapter.adapt(self)
310
# Get the program (root), and deal with case when not contained in a program
311
program = eAllContainers.find {|c| c.is_a?(Program) }
312
source_ref = program.nil? ? '' : program.source_ref
314
# An outer locator is needed since SubLocator only deals with offsets. This outer locator
316
outer_locator = Puppet::Pops::Parser::Locator.locator(adpater.extract_text, source_ref, line_offsets)
318
# Create a sublocator that describes an offset from the outer
319
# NOTE: the offset of self is the same as the sublocator's leading_offset
320
result = Puppet::Pops::Parser::Locator::SubLocator.new(outer_locator,
321
leading_line_count, offset, leading_line_offset)
329
# A heredoc is a wrapper around a LiteralString or a ConcatenatedStringExpression with a specification
330
# of syntax. The expectation is that "syntax" has meaning to a validator. A syntax of nil or '' means
331
# "unspecified syntax".
333
class HeredocExpression < Expression
334
has_attr 'syntax', String
335
contains_one_uni 'text_expr', Expression, :lowerBound => 1
340
class HostClassDefinition < NamedDefinition
341
has_attr 'parent_class', String
344
# i.e {|parameters| body }
345
class LambdaExpression < Expression
346
contains_many_uni 'parameters', Parameter
347
contains_one_uni 'body', Expression
350
# If expression. If test is true, the then_expr part should be evaluated, else the (optional)
351
# else_expr. An 'elsif' is simply an else_expr = IfExpression, and 'else' is simply else == Block.
352
# a 'then' is typically a Block.
354
class IfExpression < Expression
355
contains_one_uni 'test', Expression, :lowerBound => 1
356
contains_one_uni 'then_expr', Expression, :lowerBound => 1
357
contains_one_uni 'else_expr', Expression
360
# An if expression with boolean reversed test.
362
class UnlessExpression < IfExpression
367
class CallExpression < Expression
369
# A bit of a crutch; functions are either procedures (void return) or has an rvalue
370
# this flag tells the evaluator that it is a failure to call a function that is void/procedure
371
# where a value is expected.
373
has_attr 'rval_required', Boolean, :defaultValueLiteral => "false"
374
contains_one_uni 'functor_expr', Expression, :lowerBound => 1
375
contains_many_uni 'arguments', Expression
376
contains_one_uni 'lambda', Expression
379
# A function call where the functor_expr should evaluate to something callable.
381
class CallFunctionExpression < CallExpression; end
383
# A function call where the given functor_expr should evaluate to the name
386
class CallNamedFunctionExpression < CallExpression; end
388
# A method/function call where the function expr is a NamedAccess and with support for
389
# an optional lambda block
391
class CallMethodExpression < CallExpression
394
# Abstract base class for literals.
396
class Literal < Expression
400
# A literal value is an abstract value holder. The type of the contained value is
401
# determined by the concrete subclass.
403
class LiteralValue < Literal
407
# A Regular Expression Literal.
409
class LiteralRegularExpression < LiteralValue
410
has_attr 'value', Object, :lowerBound => 1, :transient => true
411
has_attr 'pattern', String, :lowerBound => 1
414
# Go through the gymnastics of making either value or pattern settable
415
# with synchronization to the other form. A derived value cannot be serialized
416
# and we want to serialize the pattern. When recreating the object we need to
417
# recreate it from the pattern string.
418
# The below sets both values if one is changed.
422
setPattern regexp.to_s
425
def pattern= regexp_string
426
setPattern regexp_string
427
setValue Regexp.new(regexp_string)
435
class LiteralString < LiteralValue
436
has_attr 'value', String, :lowerBound => 1
439
class LiteralNumber < LiteralValue
443
# A literal number has a radix of decimal (10), octal (8), or hex (16) to enable string conversion with the input radix.
444
# By default, a radix of 10 is used.
446
class LiteralInteger < LiteralNumber
447
has_attr 'radix', Integer, :lowerBound => 1, :defaultValueLiteral => "10"
448
has_attr 'value', Integer, :lowerBound => 1
451
class LiteralFloat < LiteralNumber
452
has_attr 'value', Float, :lowerBound => 1
457
class LiteralUndef < Literal; end
460
class LiteralDefault < Literal; end
462
# DSL `true` or `false`
463
class LiteralBoolean < LiteralValue
464
has_attr 'value', Boolean, :lowerBound => 1
467
# A text expression is an interpolation of an expression. If the embedded expression is
468
# a QualifiedName, it is taken as a variable name and resolved. All other expressions are evaluated.
469
# The result is transformed to a string.
471
class TextExpression < UnaryExpression; end
473
# An interpolated/concatenated string. The contained segments are expressions. Verbatim sections
474
# should be LiteralString instances, and interpolated expressions should either be
475
# TextExpression instances (if QualifiedNames should be turned into variables), or any other expression
476
# if such treatment is not needed.
478
class ConcatenatedString < Expression
479
contains_many_uni 'segments', Expression
482
# A DSL NAME (one or multiple parts separated by '::').
484
class QualifiedName < LiteralValue
485
has_attr 'value', String, :lowerBound => 1
488
# A DSL CLASSREF (one or multiple parts separated by '::' where (at least) the first part starts with an upper case letter).
490
class QualifiedReference < LiteralValue
491
has_attr 'value', String, :lowerBound => 1
494
# A Variable expression looks up value of expr (some kind of name) in scope.
495
# The expression is typically a QualifiedName, or QualifiedReference.
497
class VariableExpression < UnaryExpression; end
500
class EppExpression < Expression
501
has_attr 'see_scope', Boolean
502
contains_one_uni 'body', Expression
506
class RenderStringExpression < LiteralString
509
# An expression to evluate and render
510
class RenderExpression < UnaryExpression
513
# A resource body describes one resource instance
515
class ResourceBody < Positioned
516
contains_one_uni 'title', Expression
517
contains_many_uni 'operations', AttributeOperation
520
# An abstract resource describes the form of the resource (regular, virtual or exported)
521
# and adds convenience methods to ask if it is virtual or exported.
522
# All derived classes may not support all forms, and these needs to be validated
524
class AbstractResource < Expression
526
has_attr 'form', RGen::MetamodelBuilder::DataTypes::Enum.new([:regular, :virtual, :exported ]), :lowerBound => 1, :defaultValueLiteral => "regular"
527
has_attr 'virtual', Boolean, :derived => true
528
has_attr 'exported', Boolean, :derived => true
532
form == :virtual || form == :exported
542
# A resource expression is used to instantiate one or many resource. Resources may optionally
543
# be virtual or exported, an exported resource is always virtual.
545
class ResourceExpression < AbstractResource
546
contains_one_uni 'type_name', Expression, :lowerBound => 1
547
contains_many_uni 'bodies', ResourceBody
550
# A resource defaults sets defaults for a resource type. This class inherits from AbstractResource
551
# but does only support the :regular form (this is intentional to be able to produce better error messages
552
# when illegal forms are applied to a model.
554
class ResourceDefaultsExpression < AbstractResource
555
contains_one_uni 'type_ref', QualifiedReference
556
contains_many_uni 'operations', AttributeOperation
559
# A resource override overrides already set values.
561
class ResourceOverrideExpression < Expression
562
contains_one_uni 'resources', Expression, :lowerBound => 1
563
contains_many_uni 'operations', AttributeOperation
566
# A selector entry describes a map from matching_expr to value_expr.
568
class SelectorEntry < Positioned
569
contains_one_uni 'matching_expr', Expression, :lowerBound => 1
570
contains_one_uni 'value_expr', Expression, :lowerBound => 1
573
# A selector expression represents a mapping from a left_expr to a matching SelectorEntry.
575
class SelectorExpression < Expression
576
contains_one_uni 'left_expr', Expression, :lowerBound => 1
577
contains_many_uni 'selectors', SelectorEntry
580
# A named access expression looks up a named part. (e.g. $a.b)
582
class NamedAccessExpression < BinaryExpression; end
584
# A Program is the top level construct returned by the parser
585
# it contains the parsed result in the body, and has a reference to the full source text,
586
# and its origin. The line_offset's is an array with the start offset of each line.
588
class Program < PopsObject
589
contains_one_uni 'body', Expression
590
has_many 'definitions', Definition
591
has_attr 'source_text', String
592
has_attr 'source_ref', String
593
has_many_attr 'line_offsets', Integer
594
has_attr 'locator', Object, :lowerBound => 1, :transient => true
598
unless result = getLocator
599
setLocator(result = Puppet::Pops::Parser::Locator.locator(source_text, source_ref(), line_offsets))
9
require 'rgen/ecore/ecore'
10
require 'rgen/ecore/ecore_ext'
11
require 'rgen/ecore/ecore_to_ruby'
14
require 'puppet/pops/model/model_meta'
16
# TODO: See PUP-2978 for possible performance optimization
18
# Mix in implementation into the generated code
22
include Puppet::Pops::Visitable
23
include Puppet::Pops::Adaptable
24
include Puppet::Pops::Containment
27
class LocatableExpression
29
# Go through the gymnastics of making either value or pattern settable
30
# with synchronization to the other form. A derived value cannot be serialized
31
# and we want to serialize the pattern. When recreating the object we need to
32
# recreate it from the pattern string.
33
# The below sets both values if one is changed.
36
unless result = getLocator
37
setLocator(result = Puppet::Pops::Parser::Locator.locator(source_text, source_ref(), line_offsets))
44
class SubLocatedExpression
47
unless result = getLocator
48
# Adapt myself to get the Locator for me
49
adapter = Puppet::Pops::Adapters::SourcePosAdapter.adapt(self)
50
# Get the program (root), and deal with case when not contained in a program
51
program = eAllContainers.find {|c| c.is_a?(Program) }
52
source_ref = program.nil? ? '' : program.source_ref
54
# An outer locator is needed since SubLocator only deals with offsets. This outer locator
56
outer_locator = Puppet::Pops::Parser::Locator.locator(adpater.extract_text, source_ref, line_offsets)
58
# Create a sublocator that describes an offset from the outer
59
# NOTE: the offset of self is the same as the sublocator's leading_offset
60
result = Puppet::Pops::Parser::Locator::SubLocator.new(outer_locator,
61
leading_line_count, offset, leading_line_offset)
69
class LiteralRegularExpression
71
# Go through the gymnastics of making either value or pattern settable
72
# with synchronization to the other form. A derived value cannot be serialized
73
# and we want to serialize the pattern. When recreating the object we need to
74
# recreate it from the pattern string.
75
# The below sets both values if one is changed.
79
setPattern regexp.to_s
82
def pattern= regexp_string
83
setPattern regexp_string
84
setValue Regexp.new(regexp_string)
89
class AbstractResource
92
form == :virtual || form == :exported
101
class Program < PopsObject
104
unless result = getLocator
105
setLocator(result = Puppet::Pops::Parser::Locator.locator(source_text, source_ref(), line_offsets))