Getting started

This chapter is intended to help you to discover the scripting language and how it may serve your software development process.

CodeWorker is delivered with:

Binaries are available into the "bin" directory.

The scripting language adapts its syntax to the nature of the tasks to handle:

Example:

CodeWorker allows saving time to implement source code, if it disposes of a detailed design. Let start with a tiny modeling language that only understands object types and that we create just for this example:

      // file "GettingStarted/Tiny.tml":
      1 class A {
      2 }
      3
      4 class B : A {
      5 }
      6
      7 class C {
      8     B[] b
      9 }
      10
      11 class D {
      12     A a
      13     C[] c
      14 }

line 1: we declare the class A, without attributes,
line 4: we declare the class B, which inherits from A,
line 7: we declare the class C that encapsulates an array of B instances,
line 11: we declare the class D that encapsulates an association to an instance of class A and an array of C instances,

1 The parse tree

The role of the parsing is to populate the parse tree. Let suppose that, for each class, we need of the following attributes:

The description of an encapsulated attribute will require:

To discover the parse tree, we'll first populate it by hand. To do that, let run CodeWorker in console mode:

CodeWorker -console

Type the following line into the console, and be careful not to forget the final semi colon:

insert listOfClasses["A"].name = "A";
traceObject(project);

The insert keyword is used to create new branches into the parse tree. The root is named project, but hasn't to be specified, and a sub-node (or attribute) listOfClasses has been added. This sub-node is quite special: it has to contain an array of nodes that describe classes. Items are indexed by a string and are stored into their entrance order; so, the node that takes in charge of describing the class A is accessed via listOfClasses["A"]. The string "A" is assigned to the attribute listOfClasses["A"].name.

The procedure traceObject(project) shows us the first-level content of the root: the attribute listOfClasses and all its entries (only "A" for the moment). Let populate the tree with the description of the class B:

set listOfClasses["B"].name = "B";

The set keyword is used to assign a value to an existing branch of the parse tree. If this branch doesn't exist yet, a warning notices you that perhaps you have done a spelling mistake, to avoid inserting new bad nodes. But the node is inserted despite of the warning. As the language isn't typed, it allows avoiding some troubles. Let's continue:

ref listOfClasses["B"].parent = listOfClasses["A"];
traceLine(listOfClasses["B"].parent.name);

The node listOfClasses["B"].parent refers to the node listOfClasses["A"], so listOfClasses["B"].parent.name is similar to listOfClasses["A"].name. Let start filling in the tree for class C:

insert listOfClasses["C"].name = "C";
pushItem listOfClasses["C"].listOfAttributes;
local myAttribute;
ref myAttribute = listOfClasses["C"].listOfAttributes#back;

The pushItem assignment command is another way to add a new node into an array, where the item is indexed by the position of the node, starting at 0. The local keyword allows declaring a variable on the stack. This variable is also a parse tree, but not attached to the main parse tree project. For more commodities, this variable will refer to the last element of the attribute's list: myAttribute is shorter to type than listOfClasses["C"].listOfAttributes#back. Notice that the last element of an array is accessed via '#back'. Let complete the attribute b of class C:

insert myAttribute.name = "b";
ref myAttribute.class = listOfClasses["B"];
insert myAttribute.isArray = true;

The keyword true is a predefined constant string that is worth "true". The keyword false also exists and is worth an empty string.

Exercise:

Populate the parse tree with the description of class D.

2 Scanning our design with a BNF-driven script

Now, we'll describe the format of our tiny modeling language thanks to a BNF grammar (see paragraph BNF syntax for more elements about it) like it is recognized by CodeWorker :

      // file "GettingStarted/Tiny-BNF.cwp":
      1 TinyBNF ::=
      2     #ignore(JAVA)
      3     [classDeclaration]*
      4     #empty
      5     => { traceLine("this file is valid"); };
      6 classDeclaration ::=
      7     IDENT:"class"
      8     IDENT
      9     [':' IDENT ]?
      10     classBody;
      11 classBody ::= '{' [attributeDeclaration]* '}';
      12 attributeDeclaration ::= IDENT ['[' ']']? IDENT;
      13 IDENT ::= #!ignore ['a'..'z'|'A'..'Z']+;

line 1: the clause TinyBNF takes in charge of reading our design,
line 2: blanks and comments are allowed between tokens, conforming to the JAVA syntax ('/*' '*/' and '//'),
line 3: the clause classDeclaration is repeated as long as class declarations are encountered into the design,
line 4: if no class anymore, the end of file may have been reached,
line 5: the '=>' operator allows executing instructions of the scripting language into the BNF-driven script; this one will be interpreted once the file will be matched successfully,
line 6: the clause classDeclaration takes in charge of reading a class,
line 7: the clause IDENT reads identifiers and the matched sequence must be worth "class",
line 8: the name of the class is expected here
line 9: the declaration of the parent is facultative and is announced by a colon,
line 11: the clause classBody reads attributes as long as a it matches,
line 12: the clause attributeDeclaration expects a class identifier and, eventually, the symbol of an array, and the name of the attribute,
line 13: the clause IDENT reads an identifier, composed of a letter or more, which cannot be separated by blanks or comments (required by the directive #!ignore),
This BNF-driven script only scans the design ; it doesn't parse the data. Type the following line into the console to scan the design "Tiny.tml":


parseAsBNF("Scripts/Tutorial/GettingStarted/Tiny-BNF.cwp", project, 
        "Scripts/Tutorial/GettingStarted/Tiny.tml");

Output:

this file is valid

But this script isn't sufficient enough to complete the parse tree.

3 Parsing our design with a BNF-driven script

We have to improve the precedent script, called now "Tiny-BNFparsing.cwp", for building the parse tree that represents the pertinent data of the design:

      // file "GettingStarted/Tiny-BNFparsing.cwp":
      1 TinyBNF ::= #ignore(JAVA) [classDeclaration]* #empty
      2     => { traceLine("this file has been parsed successfully"); };
      3 classDeclaration ::=
      4     IDENT:"class"
      5     IDENT:sName
      6     => insert project.listOfClasses[sName].name = sName;
      7     [
      8         ':'
      9         IDENT:sParent
      10         => {
      11             if !findElement(sParent, project.listOfClasses)
      12                 error("class '" + sParent + "' should have been declared before");
      13             ref project.listOfClasses[sName].parent = project.listOfClasses[sParent];
      14         }
      15     ]?
      16     classBody(project.listOfClasses[sName]);
      17 classBody(myClass : node) ::=
      18     '{' [attributeDeclaration(myClass)]* '}';
      19 attributeDeclaration(myClass : node) ::=
      20     IDENT
      21     ['[' ']']?
      22     IDENT;
      23 IDENT ::= #!ignore ['a'..'z'|'A'..'Z']+;

line 5: the name of the class is put into the local variable sName. Note that the first time a variable is encountered after a token, it is declared as local automatically.
line 6: we populate the parse tree as we have proceeded manually,
line 9: the name of the parent class is put into the local variable sParent,
line 11: the parent class must have been declared before: the item is searched into the list of classes,
line 13: we populate the parse tree as we have proceeded manually,
line 16: clauses may accept parameters; here, the current class is passed to classBody that will populate it with attributes,
line 17: the clause classBody expects a parameter as a node; a parameter may be passed as value or node or reference,
line 19: little exercise: complete the clause attributeDeclaration that takes in charge of parsing an attribute of the class given to the argument myClass,
line 20: remember that you must parse the class name of the association here (attribute myClass.listOfAttributes#back.class refers to the associated class),
line 21: remember that you must parse the multiplicity of the association here (attribute myClass.listOfAttributes#back.isArray is worth true if '[]' is present),
line 22: remember that you must parse the name of the association here (to put into attribute myClass.listOfAttributes#back.name),
Exercise:

Complete the precedent clause attributeDeclaration to populate an attribute. You'll find the solution into file "Scripts/Tutorial/GettingStarted/Tiny-BNFparsing1.cwp".

Solution:

      // file "GettingStarted/Tiny-BNFparsing1.cwp":
      1 classBody(myClass : node) ::=
      2     '{' [attributeDeclaration(myClass)]* '}';
      3 attributeDeclaration(myClass : node) ::=
      4     IDENT:sClass
      5     => local myAttribute;
      6     => {
      7         pushItem myClass.listOfAttributes;
      8         ref myAttribute = myClass.listOfAttributes#back;
      9         if !findElement(sClass, project.listOfClasses)
      10             error("class '" + sClass + "' should have been declared before");
      11         ref myAttribute.class = project.listOfClasses[sClass];
      12     }
      13     ['[' ']' => insert myAttribute.isArray = true;]?
      14     IDENT:sName => {insert myAttribute.name = sName;};
      15
      16 IDENT ::= #!ignore ['a'..'z'|'A'..'Z']+;

line 4: the name of the class for the association is assigned to the local variable sName,
line 5: we'll need a local variable to point to the attribute's node for commodity,
line 7: the local variable myAttribute hasn't been declared here, because it disappears at the end of the scope (the trailing brace); a new node is added to the list of attributes,
line 8: the local variable myAttribute points to the last item of the list,
line 9: the class specifier of the association must have been declared,
line 11: we populate the parse tree as done by hand,
line 13: this attribute isArray is added only if the type of the association is an array,
line 14: we complete the attribute description by assigning its name,
Type the following line into the console to parse the design "Tiny.tml":


parseAsBNF("Scripts/Tutorial/GettingStarted/Tiny-BNFparsing1.cwp", project, 
        "Scripts/Tutorial/GettingStarted/Tiny.tml");

Output:

this file has been parsed successfully

4 Implementing a leader script

Now, we'll implement a little function that displays the content of our parse tree. We stop using the console here, and we'll implement the call to the parsing and the function into a leader script. This script will be called at the command line, as seen further.

We suggest to use the file extension ".cws" for non-template and non-BNF scripts.

CodeWorker command line to execute:
-script Scripts/Tutorial/GettingStarted/Tiny-leaderScript0.cws

      // file "GettingStarted/Tiny-leaderScript0.cws":
      1 parseAsBNF("Tiny-BNFparsing1.cwp", project, "Scripts/Tutorial/GettingStarted/Tiny.tml");
      2
      3
      4 function displayParsingTree() {
      5     foreach i in project.listOfClasses {
      6         traceLine("class '" + i.name + "'");
      7         if existVariable(i.parent)
      8             traceLine("\tparent = '" + i.parent.name + "'");
      9         foreach j in i.listOfAttributes {
      10             traceLine("\tattribute '" + j.name + "'");
      11             traceLine("\t\tclass = '" + j.class.name + "'");
      12             if existVariable(j.isArray)
      13                 traceLine("\t\tarray = '" + j.isArray + "'");
      14         }
      15     }
      16 }
      17
      18 displayParsingTree();

line 4: a user-defined function without parameters,
line 5: the foreach statement iterates all items of an array; here, all classes are explored,
line 7: check whether the attribute parent exists or not,
line 9: all attributes of the current class i are iterated,
line 12: perhaps the association is multiple,
line 18: a call to the user-defined function,

Output:

this file has been parsed successfully
class 'A'
class 'B'
    parent = 'A'
class 'C'
    attribute 'b'
        class = 'B'
        array = 'true'
class 'D'
    attribute 'a'
        class = 'A'
    attribute 'c'
        class = 'C'
        array = 'true'

5 Generating code with a pattern script

The source code generation exploits the parse tree to generate any kind of output files: HTML, SQL, C++, ...

A pattern script is written in the scripting language of CodeWorker, extended to be able to fuse the text to put into the output file and the instructions to interpret. It enables to process a {template-based} generation. Such a script looks like a JSP template: the script is embedded between tags '<%' and '%>' or '@'.

We'll start by generating a short JAVA class for each class of the design. It translates the attributes in JAVA and it generates their accessors:

      // file "Scripts/Tutorial/GettingStarted/Tiny-JAVA.cwt":
      1 package tiny;
      2
      3 public class @this.name@ @
      4 if existVariable(this.parent) {
      5     @ extends @this.parent.name@ @
      6 }
      7 @{
      8     // attributes:
      9 @
      10 function getJAVAType(myAttribute : node) {
      11     local sType = myAttribute.class.name;
      12     if myAttribute.isArray {
      13         set sType = "java.util.ArrayList/*<" + sType + ">*/";
      14     }
      15     return sType;
      16 }
      17
      18 foreach i in this.listOfAttributes {
      19     @ private @getJAVAType(i)@ _@i.name@ = null;
      20 @
      21 }
      22 @
      23     //constructor:
      24     public @this.name@() {
      25     }
      26
      27     // accessors:
      28 @
      29 foreach i in this.listOfAttributes {
      30     @ public @getJAVAType(i)@ get@toUpperString(i.name)@() { return _@i.name@; }
      31     public void set@toUpperString(i.name)@(@getJAVAType(i)@ @i.name@) { _@i.name@ = @i.name@; }
      32 @
      33 }
      34 setProtectedArea("Methods");
      35 @}

line 3: swapping to script mode: the value of this.name is put into the output file, knowing that the variable this is determined by the second parameter that is passed to the procedure generate (see section generate() and below). If the notation appears confusing to you (where does the writing mode ends, where does the script mode starts or the contrary), you can choose to inlay the variables in tags '<%' and '%>'.
line 4: swapping once again to script mode for writing the inheritance, if any
line 7: swapping to text mode,
line 10: we'll need a function to convert a type specifier of the tiny modeling language to JAVA, which expects the attribute's node (parameter mode is variable, instead of value),
line 13: we have chosen java.util.ArrayList to represent an array, why not?
line 18: swapping to script mode for declaring the attributes of the class
line 22: swapping to text mode for putting the constructor into the output file,
line 29: swapping to script mode for implementing the accessors to the attributes of the class
line 30: the predefined function toUpperString capitalizes the parameter,
line 34: the procedure setProtectedArea (see section setProtectedArea()) adds a protected area that is intended to the user and that is preserved during a generation process,
line 35: swapping to text mode for writing the trailing brace,
The leader script must be changed to require the generation of each class in JAVA:

CodeWorker command line to execute:
-script Scripts/Tutorial/GettingStarted/Tiny-leaderScript1.cws

      // file "Scripts/Tutorial/GettingStarted/Tiny-leaderScript1.cws":
      1 parseAsBNF("Scripts/Tutorial/GettingStarted/Tiny-BNFparsing1.cwp", project, "Scripts/Tutorial/GettingStarted/Tiny.tml");
      2
      3 foreach i in project.listOfClasses {
      4     generate("Scripts/Tutorial/GettingStarted/Tiny-JAVA.cwt", i, "Scripts/Tutorial/GettingStarted/tiny/" + i.name + ".java");
      5 }
      6

line 4: the second argument is waiting for a tree node that will be accessed into the pattern script via the predefined variable this, which has been encountered above,

Output:

this file has been parsed successfully

Let have a look to the following generated file:

      // file "Scripts/Tutorial/GettingStarted/tiny/D.java":
      package tiny;
     
      public class D {
          // attributes:
          private A _a = null;
          private java.util.ArrayList/*<C>*/ _c = null;
     
          //constructor:
          public D() {
          }
     
          // accessors:
          public A getA() { return _a; }
          public void setA(A a) { _a = a; }
          public java.util.ArrayList/*<C>*/ getC() { return _c; }
          public void setC(java.util.ArrayList/*<C>*/ c) { _c = c; }
      //##protect##"Methods"
      //##protect##"Methods"
      }

6 Expanding text with a pattern script

We'll learn about another mode of generation: expanding a file. Let suppose that you want to inlay generated code into an existing file. The way to do it is first to insert a special comment at the expected place. This comment begins with ##markup## and is followed by a sequence of characters written between double quotes and called the markup key.

Here is a little HTML file that is going to be expanded:

      // file "Scripts/Tutorial/GettingStarted/Tiny.html":
      <HTML>
          <HEAD>
          </HEAD>
          <BODY>
      <!--##markup##"classes"-->
          </BODY>
      </HTML>

The markup key is called "classes" and is put into the file like it: <!- -##markup##"classes"- ->.

Now, we'll implement a short script that is intended to populate the markup area with all classes of the design, displayed into tables:

      // file "Scripts/Tutorial/GettingStarted/Tiny-HTML.cwt":
      1 @
      2 if getMarkupKey() == "classes" {
      3     foreach i in project.listOfClasses {
      4         @ <TABLE>
      5             <TR>
      6                 <TD colspan=3><B>@i.name@</B></TD>
      7             </TR>
      8             <TR>
      9                 <TD><EM>Attribute</EM></TD><TD><EM>Type</EM></TD> <TD><EM>Description</EM></TD>
      10             </TR>
      11 @
      12         foreach j in i.listOfAttributes {
      13             @ <TR>
      14                 <TD><I>@j.name@</I></TD><TD><CODE>@
      15             @@j.class.name@@
      16             if j.isArray {
      17                 @[]@
      18             }
      19             @</CODE></TD><TD>@
      20             setProtectedArea(i.name + "::" + j.name);
      21             @</TD>
      22             </TR>
      23 @
      24         }
      25         @ </TABLE>
      26 @
      27     }
      28 }

line 2: the function getMarkupKey() returns the current expanding markup that is handled,
line 3: all classes will be presented sequentially into tables of 3 columns, whose title is the name of the class, and rows are populated with attributes,
line 12: the name, Type and Description of all attributes of the class are presented into the table,
line 15: the type is expressed in the syntax of our tiny modeling language,
line 20: the description of an attribute must be filled by the user into a protected area, so as to preserve it from an expansion to another,
The leader script has to take into account the expansion of the HTML file:

CodeWorker command line to execute:
-script Scripts/Tutorial/GettingStarted/Tiny-leaderScript2.cws

      // file "Scripts/Tutorial/GettingStarted/Tiny-leaderScript2.cws":
      1 parseAsBNF("Scripts/Tutorial/GettingStarted/Tiny-BNFparsing1.cwp", project, "Scripts/Tutorial/GettingStarted/Tiny.tml");
      2
      3 foreach i in project.listOfClasses {
      4     generate("Scripts/Tutorial/GettingStarted/Tiny-JAVA.cwt", i, "Scripts/Tutorial/GettingStarted/tiny/" + i.name + ".java");
      5 }
      6
      7 traceLine("expanding file 'Tiny0.html'...");
      8 setCommentBegin("<!--");
      9 setCommentEnd("-->");
      10 expand("Scripts/Tutorial/GettingStarted/Tiny-HTML.cwt", project, "Scripts/Tutorial/GettingStarted/Tiny0.html");
      11 //normal;

line 8: to expand a file, the interpreter has to know the format of comments used for declaring the markups. If the format isn't correct, the file will not be expanded.
line 10: be careful to call the procedure expand() and not to confuse with generate()! Remember that a classic generation rewrites all according to the directives of the pattern script and preserves protected areas, but doesn't recognize markup keys.

Output:

this file has been parsed successfully
expanding file 'Tiny0.html'...

It hasn't a great interest to present here the content of the HTML once it has been expanded, but you can display it (file "Scripts/Tutorial/GettingStarted/Tiny0.html") into your browser. You'll notice into the source code that the expanded text is put between tags <!- -##begin##"classes"- -> and <!- -##end##"classes"- ->. Don't type text into this tagged part, except into protected areas, because the next expansion will destroy the tagged part.

For discovering more about CodeWorker through a more complex example, please read the next chapter. You'll learn how to do translations from a format to another, and to use template functions or BNF clauses (very efficient for readability and extension!), and a lot of various things. But it is recommended to practice a little before.