MetaModLang (Language for Defining Box Meta models) - mn-mikke/Model-driven-Pretty-Printer-for-Xtext-Framework GitHub Wiki
This chapter describes a language that is able to define operators of the box meta-model used for the environment of the Xtext framework. The language is able define not only operators but also corresponding parameters which should be associated with some reasonable default values. Some of these default values are used when a value of the parameter is not defined in a usage of some operator. Furthermore, a code written in the language should be understandable and the meta-model generated from the language should have a reasonable structure. These two requirements are sometimes oppositional because the grammar contained in the Xtext framework has some restrictions.
Design
The following text describes what a language satisfying the requirements from the previous paragraph could look like.
Files of Stored Code
Nowadays, it is usual that each file has an extension identifying a kind of the content. Since the language is a part of the Pretty Printer and it is able to define Operators so the extension .ppo looks as a good candidate for this language. Further, it would be beneficial if a code specifying a box meta-model could be separated into more files in order to possibility to extend old box meta-models and preserve them at the same time. One possible solution is to place the import statement, which would include other box meta-models into the current one by an URI, into this language.
Constants and Enumerations
One way to make the language more comfortable for a user are constants that are well-known from common imperative languages. This language construct assigns some name to a value. Then the essence of the name is to express a significance of a value in the context of code. Since the only place for usage of values are parameters of operators (see the ideal box meta-model), where constants could be used for naming certain spacings, indentations, colors, fonts, etc., so it is reasonable to allow for defining constants of two the types namely string constants and integer constants. Furthermore, because a multiple application of particular constant is improbable, the definition of operator's local constant makes no sense. Thus the constants should be defined globally.
Listing 1
Definitions of constants in two possible types namely string and integer. The constants can also defined with a long version of keywords defining type.
int MAX_ROW_SIZE = 80
str DEFAULT_FONT = "Arial"
It also exists similar language construct to constants. This construct is called enumerations. In essence, this data type represents a named set of constants grouped by similar meaning. But the question is whether this language construct obtains own application within this language. The enumeration could be used for example for the definition of all colors, but it could be also realized by several constants. Moreover, the specification of a usage of a color would require not only the name of a color but also the name of enumeration in comparison with the definition via constants. Nevertheless, there is one application when the enumeration will represent a set of identifiers without values. This version of enumerations can be used for naming some special values with similar meaning that would be specified not in code of this language but later in the implementation of the operator written in some imperative language. Since the identifier is tool intended to reference a particular value so the local version of this language construct intended for single usage makes sense as well as the global version.
Listing 2
Definitions of enumerations in both versions. The first one is defined globally and some its item is assigned to a parameter. The second one is defined locally at the assignment of its item into a parameter.
// global enumeration
enum blanks = {TAB,WS}
{[blanks]} hc = WS
// local enumeration
{normal, bold} w = normal
Basic Operators
This kind of operators should involve all general operators whose behavior is described by a code written in common imperative language. The correct specification of a operator of this kind should contain operator's name, a link to operator's implementation describing its behavior and further definitions of corresponding operators with their default values. Since the name and the link is the most important items so they should be placed into a header of the specification and definitions of operators into the body. This division should clarify the specification of operators. The body could be enclosed into the curly brackets and individual items terminated with a semicolon as with statements in C-based language but more common form in the field of domain-specific languages is to separate the header and the body by a colon and terminate the whole body by semicolon while each item of the body has a own line.
Code formatting and syntax highlighting are two concepts of the Xtext framework implemented separately, so implementations of behavior of highlight operators would have different interfaces in comparison of the implementation of positional operators. It can be expected the similar situation with transforming operators. Thus a generated meta-model from the grammar of this language should contain a class for each kind of operators. It means from the perspective of this language that the specifications of different operator kinds have to be somehow differed. The suitable way is to put a various keyword at the begin of each specification.
So far the body of the specification was mentioned in general. These specifications should contain parameters name, a default value and also parameter's type. It makes sense to consider only three types of parameters. The first one is a integer parameter for defining some spacings, indentations, sizes, etc. The second one is string parameter for defining some fonts, colors, etc. And the last one should be able to retain an item of enumeration (see the Constants and Enumerations) ie. an identifier serving for naming the value defined in the implementation of the operator.
Listing 3
A specification of a highlight operator. The specifications begins with an assigned keyword "hloperator". The short version "hlop" also exist. Keywords "pop", "poperator" are chosen for the positional operators and further keywords "top", "toperator" belong to transforming operators. Further, the header contain operators name and a qualified name of class specifying the behavior of the operator, which is enclosed into square brackets. The body is formed form five specifications of parameters. The first two parameters are typed by local enumeration. The association of the parameter with a global enumeration is shown in the Listing 2. The specification of other types of parameters is the same as a specification of a constant shown in the Listing 1.
hloperator F [gpp.highlighting.DefaultHighlightOperatorImplementation]:
{normal, bold} w = normal
{normal, italic} i = normal
str c = "#000000"
str b = "#ffffff"
str f = "Consolas"
int h = 10
;
Alias Operators
As the method of defining basic operators was designed so the value of a parameter has to be redefined at operator's usage when the default value is not suitable. This situation may occur quite often while the required value is the same. the user should be allowed to change default values of parameters. One possible solution is to introduce alias operators that would have own default values of the same types for parameters related to the referenced operator. The alias operator should then supply the referenced operators. This kind of operators could be used for example for another highlight operator not included into the ideal box meta-model such as the highlight operator for keywords, numbers, etc. presented in the DeJonge and Brand-Visser box meta-model.
Listing 4
A specification of an alias operator specifying a formatting configuration dedicated for keywords. The operator references the F highlight operator, at which it redefines two default values. Other default values are preserved.
alias KW [F]:
w = bold
c = "#000088"
;
Complex Listing
This listing depicts what a file containing code written in MetaModLang may look like.
enum blanks = {TAB,WS}
int MAX_ROW_SIZE = 20
// horizontal operator
poperator H [gpp.formatting.HorizontalOperatorImplementation]:
int hs=1 // horizontal spacing
{[blanks]} hc = WS // horizontal character
;
// vertical operator
poperator V [gpp.formatting.VerticalOperatorImplementation]:
int vs=1 // vertical spacing
;
// table operator
poperator A [gpp.formatting.TableOperatorImplementation]:
int hs=1 // horizontal spacing
{[blanks]} hc = WS // horizontal character
int vs=1 // vertical spacing
{[blanks]} sc = WS // supplementary character
// alignment ("lcr" - The First column is aligned to the left.
// The Second column is centred. A rest of colums is aligned to the right.)
string al = "l"
;
// row operator
poperator R [gpp.formatting.RowOperatorImplementation];
// horizontal-vertical operator
poperator HV [gpp.formatting.HorizontalIfPossibleOperatorImplementation]:
int hs=1 // horizontal spacing
{[blanks]} hc = WS // horizontal character
int vs=1 // vertical spacing
int rs = MAX_ROW_SIZE // maximum row size
;
// horizontal or vertical operator
poperator HOV [gpp.formatting.HorizontalOrVerticalOperatorImplementation]:
int hs=1 // horizontal spacing
{[blanks]} hc = WS // horizontal character
int vs=1 // vertical spacing
int rs = MAX_ROW_SIZE // maximum row size
;
// indenting operator
poperator I [gpp.formatting.IndentOperatorImplementation]:
int is=1 // indent spacing
{[blanks]} ic = TAB // indent character
;
// vertical indenting operator
poperator VI [gpp.formatting.VerticalIndentOperatorImplementation]:
int ts=1 // top spacing
int bs=1 // bottom spacing
;
// font operator
hloperator F [gpp.highlighting.DefaultHighlightOperatorImplementation]:
{normal, bold} w = normal // weight
{normal, italic} i = normal // italics
string c = "#000000" // fore color
string b = "#ffffff" // background color
string f = "Consolas" // font family
int h = 10 // font height
;
// single line comment operator
toperator SC [gpp.formatting.SingleLineCommentFormattingOperatorImplementation]:
int rs = MAX_ROW_SIZE // maximum row size
string bs = "//" // begin sequence
string ws = " " // gap sequence
;
// multi line comment operator
toperator MC [gpp.formatting.MultiLineCommentFormattingOperatorImplementation]:
int rs = MAX_ROW_SIZE // maximum row size
string bs = "/*" // begin sequence
string is = " " // indenting sequence
string es = "*/" // end sequence
string ws = " " // gap sequence
;
// universal comment operator
toperator C [gpp.formatting.UniversalCommentFormattingOperatorImplementation];
Realization
The realization consists of a number of sub-steps and therefore it will be mentioned the most important.
Inheritance of Basic Operators
These realization steps relate to a specification of this language by utilizing the grammar of the Xtext framework and a subsequent generation of language's meta-model. The Basic Operators section discusses that each kind of basic operator should have own class in the generated meta-model. Specifications of operators of different kinds are similar and have the same structure so it is right to presume that the generated classes of meta-model will have the vast majority of the same attributes. Thus it would be beneficial if there was a common ancestor of these classes, which would include common attributes.
A suitable solution is to define a general grammar rule that contain all necessary definitions to define all common attributes. Moreover, the grammar rule have to contain an alternative of rule calls linked to grammar rules specifying a concrete kind. Further, these grammar rules have to contain an action for creation of a specific class into the generated meta-model and keywords identifying the concrete kind. The Defining Elements of the Parser Rule section discusses that it is good to remember actions which are enclosed into curly brackets and this type of actions is defined by a name of the created class. The following example embodies this solution.
Listing 5
A demonstration of the solution defining the inheritance in the generated meta-model.
BasicOperatorDefinition:
(PositionalOperatorDefinition | TransformingOperatorDefinition | ... )
name=ID
...
;
PositionalOperatorDefinition:
{PositionalOperatorDefinition}
('pop' | 'poperator')
;
...
Scoping
A code written in the designed language will contain a lot of links to already the defined code such as a referenced operator in the alias operator specification, a referenced parameter when the default value is redefined or some usage of a constant. These links are called cross-references in the terminology of the Xtext framework and inserts cycles into a model of the code. if the code did not contain any link, the model would be a tree structure. Since the framework creates these cycles by utilizing link's name and code can contain many names that are equal to link's name, it have to be defined scope of names that could be used for making the cycle. For example a constant, an operator and its parameter could have the same name but an alias operator need to reference only the operator.
Scopes can be defined by creation a class extending the AbstractDeclarativeScopeProvider
class and subsequent registration by utilizing the Google Guice. This class should contain definitions of scopes for particular cross references. The definition is being realized by methods whose name is formed by the prefix "scope_" plus a name of the generated class plus "_" and plus a name of the attribute where should be linked object stored. The method has to have two parameters and return the IScope
interface. The first one has the type of the generated class and the second is object of the EReference
type that represents a description of the link. Since the first parameter is the member of the model of some code, so it can be obtained an arbitrary scope by a transversal of the model and calling suitable methods of the Xtext framework. More specifically, constants of a particular type will represent a scope for the usage of some constant, a set of operators will represent a scope for some alias operator and parameters of some specific operator will represent a scope for a redefinition of some parameter of a alias operator referencing the specific operator.
Listing 6
This listing contains a grammar rule with one cross reference enclosed in square brackets and a scope provider including a method dedicated for obtaining scopes related to this cross reference.
// Grammar rule
ParameterApplication:
referencedParameter = [ParameterDefinition] '=' value = ParameterApplicationValue
;
// Scoping
public class ScopeProvider extends AbstractDeclarativeScopeProvider
{
IScope scope_ParameterApplication_referencedParameter(ParameterApplication ctx, EReference ref) {
return getReferencedParameterScope(ctx); // model transversal
}
}