Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added keyword parents, and implemented implementation inheritance #504

Merged
merged 5 commits into from
Nov 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions docs/language.rst
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ For example
Relation multiplicities are enforced by the compiler. If they are violated a compilation error
is issued.

.. note::
.. note::

In previous version another relation syntax was used that was less natural to read and allowed only bidirectional relations. The relation above was defined as ``File file [1:] -- [1] Service service``
This synax is deprecated but still widely used in many modules.
Expand Down Expand Up @@ -318,9 +318,8 @@ Values can be assigned to the remaining properties as if they are variables. To
Refinements
===========

Entities define what should be deployed.
Entities can either be deployed directly (such as files and packages) or they can be refined.
Refinement expands an abstract entity into one or more more concrete entities.
Entities define what should be deployed. Entities can either be deployed directly (such as files and packages) or they can be
refined. Refinement expands an abstract entity into one or more more concrete entities.

For example, :inmanta:entity:`apache::Server` is refined as follows

Expand All @@ -344,21 +343,23 @@ For example, :inmanta:entity:`apache::Server` is refined as follows
For each entity one or more refinements can be defined with the ``implementation`` statement.
Implementation are connected to entities using the ``implement`` statement.

When an instance of an entity is constructed, the runtime searches for refinements.
One or more refinements are selected based on the associated conditions. When no implementation is found, an exception is raised.
Entities for which no implementation is required are implemented using :inmanta:entity:`std::none`.
When an instance of an entity is constructed, the runtime searches for refinements. One or more refinements are selected based
on the associated conditions. When no implementation is found, an exception is raised. Entities for which no implementation is
required are implemented using :inmanta:entity:`std::none`.

In the implementation block, the entity instance itself can be accessed through the variable self.

``implement`` statements are not inherited.
``implement`` statements are not inherited, unless a statement of the form ``implement ServerX using parents`` is used.
When it is used, all implementations of the direct parents will be inherited, including the once with a where clause.


The syntax for implements and implementation is:

.. code-block:: antlr

implementation: 'implementation' ID 'for' class ':' statement* 'end';
implement: 'implement' class 'using' ID ('when' condition)?;
implement: 'implement' class 'using' ID ('when' condition)?
| 'implement' class 'using' 'parents';


Indexes and queries
Expand Down Expand Up @@ -386,19 +387,19 @@ Explicit index lookup is performed with a query statement
.. code-block:: inmanta

testhost = Host[name="test"]

For indices on relations (instead of attributes) an alternative syntax can be used

.. code-block:: inmanta

entity File:
string path
end

Host.files [0:] -- File.host [1]

index File(host, path)

a = File[host=vm1, path="/etc/passwd"] # normal index lookup
b = vm1.files[path="/etc/passwd"] # selector style index lookup
# a == b
Expand Down
14 changes: 12 additions & 2 deletions src/inmanta/ast/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def __init__(self, name: str, namespace: Namespace) -> None:

self.implementations = [] # type: List[Implementation]
self.implements = [] # type: List[Implement]
self.implements_inherits = False

# default values
self.__default_value = {} # type: Dict[str,object]
Expand All @@ -79,22 +80,27 @@ def __init__(self, name: str, namespace: Namespace) -> None:
self.comment = ""
self.location = None # type: Location

self.normalized = False

def normalize(self) -> None:
for d in self.implementations:
d.normalize()

for i in self.implements:
i.normalize()

self.subc = [SubConstructor(self, i) for i in self.implements]
self.subc = [SubConstructor(self, i) for i in self.get_implements()]
for sub in self.subc:
sub.normalize()

def get_sub_constructor(self) -> List[SubConstructor]:
return self.subc

def get_implements(self) -> "List[Implement]":
return self.implements + [i for p in self.parent_entities for i in p.get_implements()]
if self.implements_inherits:
return self.implements + [i for p in self.parent_entities for i in p.get_implements()]
else:
return self.implements

def add_default_value(self, name: str, value: object) -> None:
"""
Expand Down Expand Up @@ -516,8 +522,12 @@ def __init__(self) -> None:
self.constraint = None # type: ExpressionStatement
self.implementations = [] # type: List[Implementation]
self.comment = None # type: str
self.normalized = False

def normalize(self) -> None:
if self.normalized:
return
self.normalized = True
self.constraint.normalize()


Expand Down
28 changes: 28 additions & 0 deletions src/inmanta/ast/statements/define.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,34 @@ def evaluate(self) -> str:
self.copy_location(self.type)


class DefineImplementInherits(DefinitionStatement):

def __init__(self, entity_name: str, comment: str=None):
DefinitionStatement.__init__(self)
self.entity = entity_name
self.comment = comment

def __repr__(self):
"""
Returns a representation of this class
"""
return "ImplementParent(%s)" % (self.entity)

def evaluate(self):
"""
Evaluate this statement.
"""
try:
entity_type = self.namespace.get_type(self.entity)

entity_type = entity_type.get_entity()

entity_type.implements_inherits = True
except TypeNotFoundException as e:
e.set_statement(self)
raise e


class DefineImplement(DefinitionStatement):
"""
Define a new implementation for a given entity
Expand Down
2 changes: 1 addition & 1 deletion src/inmanta/parser/plyInmantaLex.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

keyworldlist = ['typedef', 'as', 'matching', 'entity', 'extends', 'end', 'in',
'implementation', 'for', 'index', 'implement', 'using', 'when', 'and', 'or', 'not', 'true', 'false', 'import',
'is', 'defined', 'dict', 'null', 'undef']
'is', 'defined', 'dict', 'null', 'undef', "parents"]
literals = [':', '[', ']', '(', ')', '=', ',', '.', '{', '}', '?']
reserved = {k: k.upper() for k in keyworldlist}

Expand Down
13 changes: 12 additions & 1 deletion src/inmanta/parser/plyInmantaParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from inmanta.ast import Location
from inmanta.ast.statements.generator import For, Constructor
from inmanta.ast.statements.define import DefineEntity, DefineAttribute, DefineImplement, DefineImplementation, DefineRelation, \
DefineTypeConstraint, DefineTypeDefault, DefineIndex, DefineImport
DefineTypeConstraint, DefineTypeDefault, DefineIndex, DefineImport, DefineImplementInherits
from inmanta.ast.constraint.expression import Operator, Not, IsDefined
from inmanta.ast.statements.call import FunctionCall
from inmanta.ast.statements.assign import CreateList, IndexLookup, StringFormat, CreateDict, ShortIndexLookup
Expand Down Expand Up @@ -335,7 +335,12 @@ def p_attr_list_dict_null(p):
p[0] = DefineAttribute("dict", p[3], Literal(NoneValue()), nullable=True)
attach_lnr(p, 1)


# IMPLEMENT
def p_implement_inh(p):
"implement_def : IMPLEMENT class_ref USING PARENTS"
p[0] = DefineImplementInherits(p[2])
attach_lnr(p)


def p_implement(p):
Expand All @@ -356,6 +361,12 @@ def p_implement_comment(p):
attach_lnr(p)


def p_implement_inh_comment(p):
"implement_def : IMPLEMENT class_ref USING PARENTS mls"
p[0] = DefineImplementInherits(p[2], comment=p[5])
attach_lnr(p)


def p_implement_when_comment(p):
"implement_def : IMPLEMENT class_ref USING ns_list WHEN condition mls"
p[0] = DefineImplement(p[2], p[4], p[6], comment=p[7])
Expand Down
26 changes: 26 additions & 0 deletions tests/test_compilation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1863,6 +1863,32 @@ def test_relation_attributes_unknown(snippetcompiler):
compiler.do_compile()


def test_implements_inheritance(snippetcompiler):
snippetcompiler.setup_for_snippet("""
entity Test:
string a
end

entity TestC extends Test:
end

implementation test for Test:
self.a = "xx"
end



implement Test using test
implement TestC using parents

a = TestC()
""")
(_, scopes) = compiler.do_compile()

root = scopes.get_child("__config__")
assert "xx" == root.lookup("a").get_value().lookup("a").get_value()


def test_double_define(snippetcompiler):
snippetcompiler.setup_for_snippet("""
entity Test:
Expand Down
14 changes: 13 additions & 1 deletion tests/test_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
from inmanta.ast.statements import define, Literal
from inmanta.parser.plyInmantaParser import parse
from inmanta.parser import ParserException
from inmanta.ast.statements.define import DefineImplement, DefineTypeConstraint, DefineTypeDefault, DefineIndex, DefineEntity
from inmanta.ast.statements.define import DefineImplement, DefineTypeConstraint, DefineTypeDefault, DefineIndex, DefineEntity,\
DefineImplementInherits
from inmanta.ast.constraint.expression import GreaterThan, Regex, Not, And, IsDefined
from inmanta.ast.statements.generator import Constructor
from inmanta.ast.statements.call import FunctionCall
Expand Down Expand Up @@ -366,6 +367,17 @@ def test_implements_2():
assert stmt.select.children[1].value == 5


def test_implements_parent():
statements = parse_code("""
implement Test using parents \""" testc \"""
""")

assert len(statements) == 1
stmt = statements[0]
assert isinstance(stmt, DefineImplementInherits)
assert stmt.entity == "Test"


def test_implements_selector():
"""Test implements with selector
"""
Expand Down