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:
an executable called CodeWorker, which runs into a shell and that requires
options on a command line,
a library called CodeWorker.lib, which may be linked to C++ applications for
extending them with parsing and source code generation feature,
some C++ headers that allow exploiting the library and that are available into the
"include" directory,
Binaries are available into the "bin" directory.
The scripting language adapts its syntax to the nature of the tasks to handle:
Acquiring the specifications of what to generate requires to be able to recognize
format, either invented for answering as fine as possible to the particularities of
the any kind of project or existing on the market and available on the script
repository (see http://www.codeworker.org/ScriptsRepository.html, in constant
improvement). A declarative language processes the scan of the format, following an
extended BNF syntax ; it accepts the intrusion of procedural instruction to
populate the parse tree.
Manipulating internal data easily and executing instructions with an expressiveness
similar to a classical system programming language. The procedural part of the
language enables to take them in charge.
Generating, expanding or tranforming text. The imperative part of the language
offers a template-based syntax that accepts instructions to navigate into the
parse tree and to take advantage of facilities brought by a programming language.
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:
name: the name of the class, C for example,
parent: the name of the parent if exists, A for class B for
instance,
listOfAttributes: an array that contains the description of encapsulated
attributes, a and c into class D for instance,
The description of an encapsulated attribute will require:
name: the name of the attribute, a into class D for instance,
class: the name of the class it belongs to, A for attribute a
for instance,
isArray: true if the attribute is an array like c for
example,
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:
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:
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:
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:
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 :
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":
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:
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:
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":
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.
CodeWorkercommand line to execute: -script Scripts/Tutorial/GettingStarted/Tiny-leaderScript0.cws
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:
CodeWorkercommand 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 { 4generate("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:
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:
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:
CodeWorkercommand line to execute: -script Scripts/Tutorial/GettingStarted/Tiny-leaderScript2.cws
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.