> For a start, I can presume that current CFEngine3.x promise file syntax 
may be 
> more than enough to cover all usages and underlying theory. Let me 
provoke 
> that there is no need to introduce any new syntactic elements.

This is almost entirely true.  There is *one* syntactic change I believe 
*may* be appropriate (and only one).  That would be to resolve the 
non-symmetricity between the "depends_on" attribute 
<https://docs.cfengine.com/lts/reference-promise-types.html#depends_on> and 
the "promisees" list 
<https://docs.cfengine.com/lts/guide-language-concepts.html>, either by 
adding a dedicated syntax for listing "dependent" promises, or by removing 
the dedicated syntax for "promisees."

As hardly anyone uses the "promisees" list and there is a dedicated syntax 
for list attributes already, I would favor removing the "promisees" syntax 
entirely.  (If it is desired it could be defined as a "promisees" 
attribute, which would increase syntax regularity.)  I will write as though 
"promisees" lists don't exist, as I favor removing that dedicated syntax in 
CFEngine 4.x.

Also, let's be clear that by *syntax* we mean *only* those elements 
described in the very first two code snippets in the Language Concepts 
documentation <https://docs.cfengine.com/lts/guide-language-concepts.html>. 
 These elements are so simple that they may be formally defined within a 
two page document.  From a *syntactic* standpoint, with no concern for 
semantics 
<https://en.wikipedia.org/wiki/Syntax_(programming_languages)#Syntax_versus_semantics>,
 
there are only two alphanumeric (non-symbolic) keywords: "bundle" and 
"body."

*Here is a rough draft of a formal syntax specification:*

------------------

A *policy*, informally, is a collection of *bundles*.  More precisely, a 
*policy* consists of zero or more *bundles* and zero or more *bodies*.

A *bundle* is a collection of zero or more *promises*.  More precisely, a 
*bundle* consists of the "bundle" keyword, a *bundle type*, a *bundle name*, 
an open curly brace, zero or more *promises*, and a close curly brace.

A *promise* consists of a *promise type* followed by a colon, an optionally 
specified *context*, a *promiser*, zero or more comma-separated *attributes*, 
and a terminating semicolon.  The *promise type* (along with its following 
colon) may be omitted from any *promise* other than the first *promise* in 
a *bundle*.  (Semantically: An omitted *promise type* shall be inferred 
from the closest preceding *promise* in the *bundle* for which a *promise 
type* is specified.  A *promise* with an omitted *context* but a specified 
*promise 
type* shall be inferred to have the *context* "any::".  A *promise* with 
both an omitted *context* and an omitted *promise type* shall have its 
*context* inferred from the immediately preceding *promise* in the *bundle*
.)

A *body* is a collection of *attributes*.  More precisely, a *body* consists 
of the "body" keyword, a *body type*, a *body name*, an open curly brace, 
one or more semicolon-terminated *attributes *(with optionally-specified 
*contexts*) and a close curly brace.  Any *attribute* within a *body* may 
be preceded by a *context*.  (Semantically: If a *context* is specified 
within a *body*, it applies to all *attributes* forward from that *context* 
until 
either another *context* is specified or the end of the *body* is reached.  
*Attributes* for which no *context* is specified shall be inferred to have 
the *context* "any::".)

An *attribute* is a key-value pair.  More precisely, an *attribute* consists 
of an *attribute name*, the characters "=>", and a *value*.

A *value* can be either a *scalar value* or a *list value*.

A *scalar value* is either an *integer*, a *real number*, or a *string*.

A *list value* is either an *integer list*, a *real number list*, or a *string 
list*.  Any *list value* consists of an open curly brace, zero or more 
comma-separated *scalar values* which are all of the same type, and a 
closing curly brace.

A *context* consists of a *class expression* followed by the characters 
"::".

*Body semantics:*

If an *attribute* has an *attribute name* which is a defined *body type*, 
the *value* of the *attribute* is expected to be a *string* containing a *body 
name* referring to a *body* which is defined somewhere in the *policy*.

------------------

In this draft syntax I only defined some italicized terms, notably *not* 
including 
"string" nor "class expression."  I believe these are much more complicated 
to define precisely in a first draft, particularly considering the quoting 
and whitespace rules <https://tracker.mender.io/browse/CFE-1921>.

I do think typing should be strengthened (strong typing) in CFEngine 4 
compared to CFEngine 3.7.

--------

One syntactic change I considered but ultimately rejected is the request 
for anonymous in-line bodies in promises (as described in CFE-2196 
<https://tracker.mender.io/browse/CFE-2196>, the help-cfengine post " 
<https://groups.google.com/forum/#!topic/help-cfengine/jTvkDyHruPI>Anonymous 
bodies - language syntax suggestion," 
<https://groups.google.com/forum/#!topic/help-cfengine/jTvkDyHruPI> and 
CFE-2064 <https://tracker.mender.io/browse/CFE-2064>.)  I wrote the post, 
and yet I reject this idea.  Why?

Consider the *purpose* of promise bodies: they are for *knowledge 
management*.

All attributes of, let us say, a "copy_from" body are *actually* attributes 
of a "files" promise.  It would not violate the simple, regular, 
predictable syntax outlined above if a "files" promise were allowed to 
*directly* include those attributes rather than referencing a "copy_from" 
body.  Why not allow this?

Likewise, why hardcode the allowable body types in the parser?  Why not 
simply define ALL attribute names related to a promise type as belonging to 
that promise type, and allow ANY arbitrary collection of them to be 
specified as a body type?

This could done (within the grammar above) with a special "bundle type." 
 Call it a "body_def" and you would get the following:

#NOT REAL CODE

bundle body_def copy_from {
  attributes:
    "source";
    "servers";
    "collapse_destination_dir"
      default_string => "false";
    "compare"
      default_string => "???",
      comment => "There is no way to accomplish default behavior 
explicitly.";
    "copy_backup"
      default_string => "true";
    "encrypt"
      default_string => "false";
    "check_root"
      default_string => "false";
    "copylink_patterns";
    "copy_size"
      default_irange => "0,inf",
      comment => "I have no idea what type this is supposed to be.";
    "findertype";
    "linkcopy_patterns";
    "link_type"
      default_string => "symlink";
    "force_update"
      default_string => "false";
    "force_ipv4"
      default_string => "false";
    "portnumber";
    "preserve"
      default_string => "false";
    "protocol_version"
      default_string => "classic",
      comment => "Can also be an int???";
    "purge"
      default_string => "false";
    "stealth"
      default_string => "false";
    "timeout"
      default_int => "default_timeout",
      comment => "Yes, I know that's not an int.";
    "trustkey"
      default => "false",
      comment => "I forgot '_string' because it wouldn't be intuitive.
                  I like 'default' better.";
    "type_check" default => "true";
    "verify" default => "false";
}

----------------------

Writing this out was an interesting exercise that highlighted a few more 
syntax questions:

1. In CFEngine 3, *each attribute name is tied to a specific type.*  In 
some CFEngine 4 cases I can conceive (such as the "default" attribute of an 
"attributes" promise in a "body_def" bundle, in the made up example above), 
it would be useful not to tie these together so tightly.  *Should* attribute 
names be tied to specific data types *by the parser*?

2. *Booleans are inconsistently handled* in CFEngine 3.  For class 
expressions, "any" is true and "!any" is false, and there is evidently a 
dedicated internal type for classes 
<https://tracker.mender.io/browse/CFE-2255> which is distinct from strings.

Menu items for attributes which may be "true" or "false" are nicely human 
readable, but if we are to expand the interoperability of the language to 
allow new promise types to be added more easily it could make sense to 
allow booleans to be an actual *type*.

For instance, why should the following syntax be necessary in CFEngine 3?

body copy_from conditionally_secure {
  be_secure::
    encrypt => "true";
  !be_secure::
    encrypt => "false";
}
  
If booleans were fully supported as a type, related to class expressions, 
you could simplify this.  Incorporating my notions of inline attributes 
instead of bodies:

  files:
    "/path/to/some/file"
      source => "/path/on/hub/to/file",
      servers => "$(sys.policy_hub)",
      encrypt => "be_secure";

3. Should there be some sort of "range" data type??  It seems that 
"copy_size" could just as well be two separate attributes, which wouldn't 
violate the key-value pair concept and would preserve regularity.

4. Very importantly, *CFEngine 3 contains multiple promises packed into a 
single "promise." <https://tracker.mender.io/browse/CFE-1843>*  This is 
viewable in promise outcomes, wherein multiple outcomes can result from a 
"single" promise.  (Nick Anderson calls them "compound promises.")  This is 
probably the single biggest discrepancy between Promise Theory *per se* and 
the CFEngine 3 implementation.

How should this be coordinated with the language semantics and syntax?

(Note that the "create" attribute of a files promise does not strictly 
align to Promise Theory at all.  This could be made the default behavior, 
and the current behavior could be accomplished with "if => 
fileexists("$(this.promiser)")".  This would solve accidental empty file 
creation <https://tracker.mender.io/browse/CFE-2329> as just a side effect.)

---------

> So, indeed, there must be a core, clear and universal approach to all 
> promises, their dependencies etc. 
> 
> I would dare to add, also, that extending promise types within the 
monolithic 
> binary may not work long-term, making CFEngine's code hard to maintain 
and 
> adapt to all usage scenarios (think the needs of embedded devices vs. 
data- 
> center clusters). Given the above formal language specification, parts of 
> CFEngine could be split out to /modules/, as in dynamically-linked 
libraries. 

I completely agree with both points here.  There will always be new needs 
requiring new additional *promise types* as computer applications in the 
world continue to grow exponentially.

For example, Promise Theory is more than adequate to model cloud 
orchestration, yet CFEngine offers no orchestration features.  You might 
have a "machines:" or "vms:" promise type, with attributes such as 'os => 
"redhat 6"', 'subnet => "10.10.10.0/24"'—all sorts of things.  These would 
require updates to the language specification but these should be *semantic* 
changes 
rather than *syntactic*. 
<https://en.wikipedia.org/wiki/Syntax_(programming_languages)#Syntax_versus_semantics>

*(Note: I actually wrote the text from here down before I wrote any other 
part of this post.)*

Drawing on the pure mathematical model of Promise Theory, it should be 
possible to define a standard by which *any* promises (and promisees and 
promisers) can be expressed—and if that notation is compatible wherever 
possible to the CFEngine 3 policy language, the connection can be quite 
straight forward.  What I'm talking about here would probably be classified 
as a formal grammar <https://en.wikipedia.org/wiki/Formal_grammar>.  C has 
an official formal grammar, though not available for free 
<http://www.iso.org/iso/home/store/catalogue_ics/catalogue_detail_ics.htm?csnumber=57853>.
 
 (It's also summarized in K&R 
<https://www.safaribooksonline.com/library/view/the-c-programming/9780133086249/app01.html>.)
 
 POSIX shell has a formal grammar 
<http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_10>
.

Now whether or not CFEngine gets a formal grammar (which it should, 
eventually), it should certainly have a language *specification*.  They're 
not the same thing.  In actual fact, because of the (intended) regularity 
of CFEngine, I *suspect* that a formal grammar for the CFEngine 4 policy 
language would actually be simpler than the full language specification!

The language specification would include the defined *behavior* of all the 
promise types and attributes, whereas the grammar would define simply how 
elements are structured.  If the regularity between bundles and bodies 
could be captured in the formal grammar, any manner of promise could be 
*written* in the language, and people could learn the language without 
necessarily learning the particular bundle types, promise types and 
attributes available in a particular *parser *for the "Promises Language."

I love your suggestions regarding splitting out parts of CFEngine into 
modules.

Best,
--Mike Weilgart
Vertical Sysadmin, Inc.

-- 
You received this message because you are subscribed to the Google Groups 
"dev-cfengine" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/dev-cfengine/95f146bc-49ef-43f4-a483-5988a74068d9%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to