Skip to content
This repository has been archived by the owner on Aug 13, 2020. It is now read-only.

CalvinScript

Harald Gustafsson edited this page Dec 20, 2016 · 14 revisions

CalvinScript syntax

For a formal definition, see Appendix A

Basically, an application consists of actor instances and connections between the ports of the actor instances, forming a data flow graph. To allow reuse and clean scripts, it is also possible to define components. An application script can also contain port properties and deployment rules.

Data flow graph

The data flow graph is in all respects the application.

Actor instantiation

src : io.FileReader(file="~/input.txt")
^     ^  ^          ^    ^
|     |  |          |    |
|     |  |          |    +-- argument 
|     |  |          +------- argument identifier     
|     |  +------------------ actor type
|     +--------------------- namespace (required, except for components, see below)
+--------------------------- instance name (i.e. variable)

Connection setup

src.out > dst.in
^   ^     ^   ^
|   |     |   | 
|   |     |   +-- destination actor inport 
|   |     +------ destination actor instance 
|   +------------ source actor outport
+---------------- source actor instance

Example script

src : io.FileReader(file="~/input.txt")
dst : io.StandardOut()

src.out > dst.in

Literal values on inports

CalvinsScript allows the use of literal values on inports as a convenience. In practice, that means that instead of writing:

src : std.Constant(data={"foo":1, "bar":2})
snk : io.Print()
src.token > snk.token

you can write:

snk : io.Print()
{"foo":1, "bar":2} > snk.token

Components

It is possible to group actor instances and connections together in a component. A component behaves just like an actor, and allows for easy reuse and hiding of implementation details as well as an extension mechanism for Calvin.

Component definition

A component is defined as follows:

 # TokenLogger will pass token unchanged from input to output, while logging each token to file. 
 
 component TokenLogger(logfile) token -> token { <data flow graph> }
 ^         ^           ^        ^        ^       ^
 |         |           |        |        |       |
 |         |           |        |        |       +-- implementation, see below
 |         |           |        |        +---------- output port declaration
 |         |           |        +------------------- input port declaration
 |         |           +---------------------------- argument identifier
 |         +---------------------------------------- component type
 +-------------------------------------------------- keyword (component definition start)

Notice the lack of namespace in the definition: until a component is installed it is local to the script file.

To actually make the component useful, we have to define its internal data flow graph and its relation to the ports declared by the component. The internal data flow is described exactly as above, with the exception for the connections to/from the component ports.

component TokenLogger(logfile) token -> token {
    tee    : std.Tee() # Tee duplicates tokens on inport 'in' to outports 'out_1' and 'out_2'
    logger : io.FileWriter(file=logfile)
    
    .token > tee.in
    tee.out_1 > logger.in
    tee.out_2 > .token
}

Instantiation of actors is the same as before, but the first and the last of the connections are slightly different in that they do not follow an 'actor.port' pattern, but have unqualified ports. The scope of the actor instances and argument identifiers used in the component is limited by the curly braces.

.token > tee.in
  ^       ^
  |       | 
  |       +-- qualified port
  +---------- unqualified port

Whenever an unqualified port is encountered, it is taken to refer to the ports declared by the component.

So, to extend the first example with logging functionality we could write the following script:

component TokenLogger(logfile) token -> token {
    """A simple token logger."""
    
    tee    : std.Tee() # Tee duplicates tokens on inport 'in' to outports 'out_1' and 'out_2'
    logger : io.FileWriter(file=logfile)
      
    .token > tee.in
    tee.out_1 > logger.in
    tee.out_2 > .token
}  

src : io.FileReader(file="~/input.txt")
log : TokenLogger(logfile="~/log.txt")
dst : io.StandardOut()

src.out > log.token
log.token > dst.in

One point to notice here is how arguments are propagated from the component to its constituent actors. The component definition states the argument identifier ('logfile') which is passed as argument to the io.FileWriter instance, and its value is defined in when instantiating the TokenLogger (logfile="~/log.txt").

N.B. Calvin does not distinguish between components and actors, so once defined a component can be used anywhere, including other component definitions.

Installing components

Components that are generally useful can be installed in the ActorStore under a suitable namespace using the installer tool. If the above component was installed under e.g. namespace logging we would be able to use it as follows:

src : io.FileReader(file="~/input.txt")
log : logger.TokenLogger(logfile="~/log.txt")
dst : io.StandardOut()

src.out > log.token
log.token > dst.in

Documentation for installed components will be available in the DocStore, and the description will be extracted from the docstring (triple-quoted string following the opening curly brace). The documentation for the input and output ports will be automatically derived from the "real" ports of the actors in the component definition.

Deployment definitions

The deployment definitions defines port connection properties and rules for placement and scaling.

Port properties

Port properties can for example be used to define that a port should deliver or collect tokens according to a specific model among its connected peers' ports. See page Port Properties for details.

src.token[in/out](routing="balanced")
^   ^    ^          ^      ^
|   |    |          |      |
|   |    |          |      +-- property value 
|   |    |          +--------- property identifier     
|   |    +-------------------- optional specify [in] or [out] to handle ambiguity on ports with same name
|   +------------------------- port name
+----------------------------- actor instance name (i.e. variable)

Deployment and scaling rules [alpha-level]

Deployment rules can be defined in the script, currently the support is limited and the error handling is insufficient. A simple rule can be defined as follows:

rule myrule: node_attr_match(index=["node_name", {"organization": "com.ericsson"}])
^    ^       ^               ^     ^
|    |       |               |     |
|    |       |               |     +-- argument 
|    |       |               +-------- argument identifier     
|    |       +------------------------ requirement expression on placement or scaling
|    +-------------------------------- rule name
+------------------------------------- rule keyword

Rules can also be combined by several previously defined rules and requirement expressions using the & operator for intersection. The ~ operator can be used in front of a requirement expression to obtain the inverted sub-set (this should be used very limited since not well tested and could lead to a large set). A rule with | operation between requirement expressions forms a union set. The resulting rule can then be combined in other rules and maintain the union grouping.

See TODO:RequirementExpressions page for details on which requirement expressions that are available.

Apply rules on actors [alpha-level]

The rules defined can then be applied to actors and components, currently all top-level and only top-level actors and components need to have rules applied once.

apply src, snk: myrule <& other rule or requirement expression>
^     ^         ^       ^
|     |         |       |
|     |         |       +------- optional rules and requirement expressions
|     |         +--------------- rule and requirement expressions
|     +------------------------- actor instance name(s)
+------------------------------- apply keyword

CalvinScript argument types

Finally, the types of arguments that CalvinScript understands are JSON-like:

  • number : integer or float, e.g. 42, 3.14
  • string : double quoted text, e.g. "foo in the bar"
  • literal string : string preceded by ! means no interpretation of escapes, e.g. !"([a-zA-Z][a-zA-Z0-9_]*\n)"
  • list : a bracketed list, e.g. [1, 2, 3, "foo"]
  • dictionaries : a curly bracketed list with key/value pairs, e.g. {"key":"value", "foo":[1, 2, 3]}. Keys must be strings.
  • boolean : true or false
  • null : null

Appendix A. Formal syntax

keywords : component, define

N.B. The list of keywords may grow

tokens : COMMENT    (/\*(.|\n)*?\*/)|(\#.*)
         IDENTIFIER [a-zA-Z][a-zA-Z0-9_]* 
         STRING     !?".*?"
         NUMBER     -?\d+(?:\.\d*)?
         DOCSTRING  """(.|\n)*?"""
         LPAREN     '('
         RPAREN     ')'
         LBRACE     '{'
         RBRACE     '}'
         LBRACK     '['
         RBRACK     ']'
         DOT        '.'
         COMMA      ','
         COLON      ':'
         GT         '>'
         EQ         '='
         RARROW     '->'
         FALSE      'false' 
         TRUE       'true'
         NULL       'null

N.B. Keywords are not allowed as identifiers.

script         ::= constdefs compdefs opt_program

constdefs      ::= 
                 | constdefs constdef
                 | constdef

constdef       ::= DEFINE IDENTIFIER EQ argument

compdefs       ::= 
                 | compdefs compdef
                 | compdef

compdef        ::= COMPONENT qualified_name LPAREN identifiers RPAREN identifiers RARROW identifiers LBRACE docstring program RBRACE

docstring      ::= 
                 | DOCSTRING

opt_program    ::= 
                 | program

program        ::= program statement
                 | statement

statement      ::= assignment
                 | link

assignment     ::= IDENTIFIER COLON qualified_name LPAREN named_args RPAREN

link           ::= port GT port
                 | argument GT port

port           ::= IDENTIFIER DOT IDENTIFIER
                 | DOT IDENTIFIER

named_args     ::= 
                 | named_args named_arg COMMA
                 | named_args named_arg

named_arg      ::= IDENTIFIER EQ argument

argument       ::= value
                 | IDENTIFIER

value          ::= dictionary
                 | array
                 | bool
                 | null
                 | NUMBER
                 | STRING

bool           ::= TRUE
                 | FALSE

null           ::= NULL

dictionary     ::= LBRACE members RBRACE

members        ::= 
                 | members member COMMA
                 | members member

member         ::= STRING COLON value

values         ::= 
                 | values value COMMA
                 | values value

array          ::= LBRACK values RBRACK

identifiers    ::= 
                 | identifiers IDENTIFIER COMMA
                 | identifiers IDENTIFIER

qualified_name ::= qualified_name DOT IDENTIFIER
                 | IDENTIFIER