Petri Java
Petri Java
3 December 2002
Contents
1 An Introduction to Pencil 3
1.1 Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Related Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Goals of Pencil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3.1 Intuitive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3.2 Object-Oriented . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3.3 Portable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3.4 Powerful . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3.5 Robust . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2 Tutorial 6
2.1 A First Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2 Compiling and Running Pencil Spec Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.3 More Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3 Reference Manual 10
3.1 Grammar Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.2 Lexical Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.2.1 Line Terminators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.2.2 Comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.2.3 Whitespace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.2.4 Tokens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.2.5 Identifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2.6 Keywords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2.7 Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2.8 Separators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2.9 Embedded Java Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.3 Petri Net Specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.3.1 Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.3.2 Package Declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.3.3 Net Declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.3.4 Constant Declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.3.5 Place Declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.3.6 Transition Declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
4 Project Plan 15
4.1 Team Responsibilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.2 Project Timeline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.3 Software Development Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.4 Project Log . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1
5 Architectural Design 17
5.1 Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
5.2 The Runtime Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
5.3 Error Recovery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
6 Testing Plan 22
6.1 Goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
6.2 Hypothesis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
6.3 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
6.3.1 Phase I . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
6.3.2 Phase II . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
6.3.3 Phase III . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
6.4 Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
6.5 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
6.5.1 Phase I . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
6.5.2 Phase II . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
6.5.3 Phase III . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
A Pencil Grammar 25
2
Chapter 1
An Introduction to Pencil
The Pencil language is designed to help developers quickly prototype, simulate and build their systems using
the formal language of Petri nets. Using Pencil, it is possible to specify and model a system and to put
abstract models directly into action. Pencil compiles Petri net specifications into objects defined in the Java
language, allowing Pencil to seamlessly integrate with real-world enterprise applications.
Pencil is designed to be simple, intuitive, flexible, formal, powerful and highly modular. The formal
properties allow theoreticians to leverage the rich literature on Petri nets in modelling and designing systems.
The modularity and Java integration allows working programmers to combine verified modules with no
additional work. The simple syntax (similar to Java and C++) and automatic translation mechanism help
reduce implementation errors. Pencil extends the mathematical model of Petri nets so that application
behavior can be described simply and economically. This combination of features make Pencil a good bridge
between modeling and implementation.
1.1 Background
Petri nets are a graphical and mathematical modeling tool which are useful for modeling systems with con-
current, asynchronous, distributed or parallel properties. They have been observed to have broad application
in modeling finite state machines, parallel activities, dataflow computations, communication protocols, sy-
chronization control, discrete event systems, and asynchronous circuits [1]. They were introduced by Carl
Adam Petri in 1962 [3]. In early 1970’s MIT was very active in the research of Petri nets. Since the late
1970’s, European researchers have organized workshops and published conference proceedings on Petri nets
[1]. Murata’s IEEE paper [1] and Peterson’s book [2] are good introductions to the subject.
The Petri nets can mathematically be described as follows:
Let denote the set of nonnegative integers. A Petri net with inhibitor arcs is a 6-tuple PN =
(P, T, F, I, V, m0 ), where:
• P = {p1 , p2 , . . . , p|P | } is a set of places, where |P | denotes the cardinality of set P ,
• T = {t1 , t2 , . . . , t|T | } is a set of transitions, where |T | denotes the cardinality of set T and P ∩ T = ∅,
• F ⊆ (P × T ) ∪ (T × P ),
• I ⊆ (P × T ),
• V is a weight function: F 7→ , and
• m0 is the initial marking P 7→ .
Petri nets can be viewed as bipartite directed multigraphs. The set of nodes is divided into two disjoint
sets: P and T . An arc in F connects a pair of nodes in P × T or T × P . The mapping V assigns non-negative
integers to arcs. An arc assigned with number k denotes k–parallel arcs between the same pair of nodes.
A marking m ∈ ( ∪ 0)n is a mapping from every place p to a non-negative integer. If the number k is
3
assigned to the place p, we say that there are k tokens on place p. The number of tokens on place p under
marking m is represented by m(p). The initial marking m0 is the districution of tokens between the places
of the net before any action is taken. The input place of a transition t , denoted as t − , is a set of places
{p | p ∈ P, (p, t) ∈ F }. The output place of a transition t is a set of places defined as {p | p ∈ P, ∃t ∈ T such
that (t, p) ∈ F }, and is denoted by t+ . The functions t− (p) = i is defined as (p, t) ∈ F and V ((p, t)) = i.
Similarly t+ (p) = i is defined as (t, p) ∈ F and V (t, p) = j.
A transition t is said to be enabled given a marking m iff ∀p ∈ t− , m(p) ≥ t− (p). An enabled transition
is ready to fire. When a transition fires, it removes tokens from its input places and puts tokens in its
output places. An enabled transition may or may not fire, and there may be more than one transition
enabled in a given marking, but only one transition can fire at a time. If the current marking of the net is
m, t is the fired transition, and m0 is the marking after the firing of t, the relation between m and m0 is
m0 (p) = m(p) − t− (p) + t+ (p), for every p ∈ P .
An inhibitor arc in I is an arc connecting a place and a transition. The connected transition cannot
fire if the input place along the inhibitor arc contains at least one token. A transition with inhibitor arcs is
enabled iff ∀p ∈ t− , m(p) ≥ t− (p) and every input place along the inhibitor arcs contains no token. After
the transition fires, no token is removed through the inhibitor arc. Inhibitor arcs give Petri nets the ability
to test “zero”. This extends the modeling power of Petri nets to the level of Turing machines.
1.3.1 Intuitive
The foremost goal in the creation of this language was to make it easy to learn and straightforward to
program. The user should be left to concentrate on the structure and design of her Petri net, not the syntax
of the modeling language. We created Pencil to be consistent and intuitive, even to users who have limited
programming experience.
1.3.2 Object-Oriented
By supporting the concept of objects, each component of the net has its own attributes. By breaking down
the net into its components, we believe that most users will find Pencil to be conceptually intuitive to use.
1.3.3 Portable
The compiler will accept a Pencil specification as an input file and generate Java source code. This source
code can be integrated with a larger Java project and compiled with any Java compiler. Because Java is
Pencil’s target platform, portability is limited only by the availability of a Java Virtual Machine.
4
1.3.4 Powerful
Pencil supports Petri net programming using many different models. Pencil can be used to simulate
finite-state machines, concurrency, dataflow computation, communication protocols, synchronization con-
trol, producer-consumer systems with priority, formal languages, and multiprocessor systems.
Pencil will initially support basic implementations of all types of nets. As its base definitions grow, Pencil
will only become more crucially useful to more people, modeling virtually any type of net that can be drawn
on a piece of paper. Modeling a net with Pencil, errors with the structure of the net itself will be immediately
obvious.
1.3.5 Robust
With the power and simplicity of a robust Petri net specification engine, the time to design, implement and
test a complex system can be reduced by an order of magnitude. Pencil’s simple and intuitive language
syntax ensures that most errors are detected at compile time and that compiled Pencil code is as accurate
as it can be.
5
Chapter 2
Tutorial
A Pencil Petri net specification is a sequence of place and transition definitions. Input and output arcs
can be defined on the places or transitions. Transitions can be associated with Java code to be executed
when they fire. Pencil was designed to make the translation from a Petri Net diagram to a specification as
straightforward as possible.
Figure 2.1 shows a very simple Petri Net. There are two places, p1 and p2. There is one transition, t1.
There is one token in p1. t1 has one input arc from p1 and one output arc to p2. Because t1’s input place
(p1) has a token, we say that t1 is enabled, meaning it is ready to fire. (Arcs may be labelled with a weight.
The arcs in Figure 2.1 are unlabelled, meaning they have the default weight of 1. A transition is enabled if
each of its input places has a number of tokens equal to or greater than the weight of the arc connecting it.)
To represent this simple Petri net in the Pencil language, you might use a specification like the following:
net FirstNet ;
place p1(=1), p2 ;
transition t1 {
in: p1 ;
out: p2 ;
}
FirstNet defines two places and a transition, as in Figure 2.1. The notation p1(=1) means that p1 has
an initial marking of one token (i.e., has one token to start). When run, FirstNet will see that t1 is enabled
and fire it, removing one token from p1 and placing one in p2. We haven’t defined any actions for Pencil to
take when t1 fires, so there will be no output to the screen.l
We can make our example more interesting by adding embedded Java code to the transition definition:
transition t1 {
in: p1 ;
out: p2 ;
<% System.out.println("Hello, world!") ; %>
}
6
Now the statement System.out.println("Hello, world!") is associated with t1. When t1 fires, this
statement will be executed, printing the string “Hello, world!” to the standard output.
In both of the specifications above, the transition fires as soon as it is enabled. Sometimes we want a
transition to fire only when an event occurs. To do this, we can change the transition property ‘fire’ to
‘onCall’, as follows:
transition t1 {
in: p1 ;
out: p2 ;
fire: onCall ;
<% System.out.println("Hello World!") ; %>
}
Now, t1 will not fire until it is requested to do so by a client program. Pencil will add a method called
t1() to the generated Java class. When a Java client calls the method, it will block until t1 is enabled, then
fire the transition and return.
This will produce a file called MyPetriNet.java that contains java code implementing this Petri net.
Compile the java file using javac and then run it using java:
$ javac MyPetriNet.java
$ java MyPetriNet
2 2
•• p1 p3
t3
p4 .
2
Figure 2.3 shows a more complicated Petri Net example. Notice that several of the arcs are labelled with
weights. This Petri Net could be specified in Pencil as follows:
net SecondNet ;
7
transition t1 {
in: p1(2) ;
out: p2, p3 ;
}
transition t2 {
in: p2, p4(2) ;
out: p1 ;
}
transition t3 {
in: p3 ;
out: p1, p4(2) ;
}
Note the arc definitions of the form p1(2). This means that p1 has a weight of 2 tokens.
The Pencil language allows you to define arcs on either transitions or places. This allows the programmer
the flexibility to specify a Petri net in whichever form is most convenient. The same Petri net could also be
defined as follows:
net SecondNetPlaceDef ;
place p1(=2) {
in: t3, t2 ;
out: t1(2) ;
}
place p2 {
in: t1 ;
out: t2 ;
}
place p3(=1) {
in: t1 ;
out: t3 ;
}
place p4 {
in: t3(2) ;
out: t2(2) ;
}
We conclude this tutorial with a complete example drawn from the Petri net in Figure 2.3.
net LiveACnet;
transition t1{
in: p1 ;
out: p2, p4 ;
8
p1
• .
t1 t2 t3
p4 p2 p3
.
t4
Figure 2.3: A final Petri net example.
transition t2{
in: p3, p2 ;
out: p1 ;
}
transition t3 {
in: p3 ;
out: p1 ;
}
transition t4 {
in: p4 ;
out: p3 ;
}
9
Chapter 3
Reference Manual
3.2.2 Comments
Both C- and C++-style comments are supported. A C-style comment begins with the characters /* and
ends with the characters */. Any sequence of characters may appear inside of a C-style comment except the
string ‘*/’. C-style comments do not nest. A C++-style comment begins with the characters // and ends
with a line terminator.
/* and */ have no special meaning inside comments beginning with //. // has no special meaning inside
comments beginning with /*.
3.2.3 Whitespace
Whitespace is defined as the ASCII space, horizontal tab and form feed characters, as well as line terminators
and comments.
3.2.4 Tokens
There are five classes of tokens: identifiers, keywords, constants, separators and embedded Java blocks.
Whitespace is ignored except as a token separator. Whitespace is sometimes required to separate adjacent
10
tokens that might otherwise be combined into one token (i.e., identifiers, keywords and constants).
Token formation is greedy: the input is searched for the longest string of characters that could constitute
a token.
3.2.5 Identifiers
An identifier is a sequence of letters or digits, the first of which must be a letter. There is no limit on the
length of an identifier.
Two identifiers are the same if they have the same Unicode character for every letter and digit. Identifiers
that have the same external appearance may not by identical. For example, the Latin capital letter ’A’ and
the Greek capital letter ‘A’ (“Alpha”) are different Unicode characters that have the same appearance when
displayed.
3.2.6 Keywords
The following identifiers are reserved for use as keywords, and may not be used otherwise:
3.2.7 Constants
Constants are a sequence of ASCII digits representing an integer literal. Constants are unsigned, and the
integer represented must be within the range of the Java primitive type int.
Constant → digit+
3.2.8 Separators
The following ASCII characters are separators:
{ } [ ] : ; , .
11
3.3 Petri Net Specifications
A Pencil Petri net specification is a file that contains a net declaration followed by any number of place and
transition declarations with an optional concluding Java block. A specification is compiled into a Java class
that extends Thread. Calling the method start() on the generated class will begin execution of the Petri
Net.
3.3.1 Names
A name is bound by a declaration and is available at any point in the specification that follows the declaration.
A name must be unique within the specification.
12
3.3.4 Constant Declarations
A constant declaration associates a name with an integer constant.
13
T ransitionDeclaration → ‘transition’ T ransition (‘,’ T ransition) ∗ ‘;’
| ‘transition’ T ransition T ransitionDef inition
T ransition → Identif ier
T ransitionDef inition → ReturnV alue? ‘{’ T ransitionAttribute + F iringRule? JavaBlock? ‘}’
ReturnV alue → ‘[’ ‘ret’ Expression ‘]’
Expression → (Constant | V ariable)
V ariable → Identif ier
T ransitionAttribute → T ransitionInputs | T ransitionOutputs | F iringRule
T ransitionInputs → ‘in’ ‘:’ P laceList ‘;’
T ransitionOutputs → ‘out’ ‘:’ P laceList ‘;’
F iringRule → ‘fire’ ‘:’ (‘immediate’ | ‘onCall’) ‘;’
P laceList → P laceArc (‘,’ P laceArc)∗
P laceArc → P laceN ame ArcW eight?
P laceN ame → Identif ier
14
Chapter 4
Project Plan
15
10-01-2002 Project initiated
10-08-2002 Language whitepaper complete
10-09-2002 Code conventions, first draft
10-27-2002 Development environment configured and online
10-27-2002 Runtime prototype
10-29-2002 Grammar, first draft
11-07-2002 Language tutorial, first draft
11-07-2002 Language reference manual, first draft
11-10-2002 Testing Phase I begins
11-11-2002 Code conventions, final draft
11-13-2002 Parser and lexer, first working version
11-14-2002 Project architecture document complete
11-15-2002 Compiler back-end, first working version
11-17-2002 Testing Phase II begins
11-20-2002 Work on graphical simulator begins
11-28-2002 Error recovery complete
11-29-2002 Parser and compiler back-end, final versions
12-01-2002 Graphical simulator, first version
12-03-2002 Text simulator, first version
16
Chapter 5
Architectural Design
5.1 Architecture
The Pencil compiler consists of several major blocks which are common in compiler designs: lexer, parser,
symbol table, error handler, the runtime environment, and the code generator. The relationship between
these components is demonstrated in Figure 5.1. The code generator is not explicitly shown; but with the
parser these two components are implemented using a Director and Builder design pattern. The input to the
compiler are Pencil specification files (which have, by convention, the suffix .pen) and the final output from
the compiler is translated Java code. The compiler takes one pen file once a time, and translates it to Java.
The lexer is implemented in JFlex, a version of lex for Java. The parser is generated by CUP, a version of
yacc for Java. The error handler is deals with error recovery when syntax errors are encountered. It informs
the code generator not to generate the final output whenever a syntax error is found, and prompts the user
with an appropriate error message. The runtime system is a set of predefined classes that are inherited
by the generated Java classes. The runtime classes provide the basic computation necessary for Petri net
simulation and execution. Between the parser and the code generator there is a clear interface making these
two components highly modular. The Builder pattern allows the change of front-end and back-end design
not to affect the other part of the design if the interface between them is followed.
The entry point of the compiler is the class pencil.Main. The main() method parses the command
line argument, and initializes the parser to begin processing the Pencil source file. The interface between
the lexer and the parser is the lexer’s next token() method. The parser’s task is the “director” of the
code generator, which is the “builder” of the target code. Semantic actions on parser productions invoke a
set of builder method, constructing an abstract Petri net representation from the source code. If a syntax
error occurs, the parser invokes the error handler (SynErrHandler). The error handler prints an approriate
error message, and also informs the builder that a syntax error has occured. When parsing is finished. the
build() method of the Builder class is invoked, and the target code is generated.
The Builder pattern most suits a compiler with different targets. The separation of a Director and a
Builder not only allows the creation of different targets with the same front-end components (the parser,
lexer, error handler, and symbol table), but also minimizes the effort to improve both the front–end and
the back–end, and add on more language features. The parser is the Director, and it directs the concrete
instances of NetBuilder to build the target, with a sequence of calls. The Java interface NetBuilder defines
17
the interface between the parser and the concrete builders. A Builder class implements this interface and
interprets the sequence of calls from the Director as instructions for building a Petri net class file. The
diagram 5.2 demonstrates the relationship between the parser (Director) and the NetBuilder (Builder).
Parser NetBuilder
+startPlace(name:String): void
+endPlace(): void
+setMarking(n:int): void
+setBound(): void
+build(): void
+startTransition(): void
+setReturn(): void
+setFire(): void
+setJava(): void
+addTransition(): void
+endTransition(): void
+setPackage(): void
+setNet(): void
+addParam(): void
+addConstraint()
The pencil.Main() instantiates a concrete Builder according to the command line arguments. There
are implementations of the NetBuilder interface for generating stand-alone Java code, or Petri net simulators
(both graphical and text-mode), depending on the user’s preference. The following code fragment setups an
appropriate builder and passes it to the director:
NetBuilder b = null ;
if( debug ) {
b = new DebugBuilder() ;
} else if (textSim){
b = new SimBuilder();
} else{
b = new DefaultBuilder() ;
}
p.setBuilder(b) ;
The interface between the parser and the NetBuilder defines an abstract model of Petri nets. The
capture of the abstract model of Petri nets is crucial both to the Director and the Builder. Language
extensions and improved back-end components can be introduced without breaking the Director-Builder
interface. The interface consists mainly of methods to inform the Builder of place definitions, transition
definitions, transition firing types and arcs between places and transitions. More specifically, the following
methods are used to set the place definitions:
The startPlace() is called when the parser finds the beginning of a place definition in the source code.
The parser then communicates the initial marking and the upper-bound (on the number of tokens this place
18
can have most) information by calling setMarking and setBound. Finally the endPlace() method is used
to signal the end of a place definition.
The transition-related methods are defined in a similar way. They are:
The sequence diagram in Figure 5.3 illustrates the flow of calls between pencil.Main, the parser, and
NetBuilder.
<<create>>
<<create>>
setBuilder()
parse()
setNet()
startPlace*()
starTransition*()
build()
public HelloWorld() {
19
pencil.runtime.Place p2 = new pencil.runtime.Place() ;
add(p2) ;
// ...
PlaceDecl ::=
PLACE PlaceList SEMI
// ....
/* Error Productions */
// catch missing semicolon.
| PLACE PlaceList:err
{: parser.errHandler.onErrProduction(SynErrHandler.ERR_MISSING_SEMI,
errleft, errright);
parser.synErr = true;
:}
;
The panic mode technique is for other non–predictable parsing errors. The placement of the “error”
tokens are not quite straightforward. The following fragment of the production demonstrates the use of the
“error” token:
PlaceDecl ::=
PLACE PlaceList SEMI
| PLACE Place PlaceDef
/* Panic Mode */
| PLACE error:err
{: parser.errHandler.onError(SynErrHandler.ERR_PLACE_MISSING_COMMA_SEMI,
errleft, errright);
parser.synErr = true;
20
:}
21
Chapter 6
Testing Plan
6.1 Goals
No test plan can aspire to catch every bug in a program. It is not the goal of this project to test every
possible input to the Pencil compiler. Rather, these tests are designed to lay out a systematic approach to
finding inconsistencies in the way data is treated throughout the development cycle. With careful choice of
unit tests, regression tests, white box and black box tests, the development process can evolve smoothly. The
goal for the tests deployed against the Pencil code is to test smartly–to write enough tests to test thoroughly,
but not so many that time is wasted on insignificant coding–and to run these tests at least daily.
6.2 Hypothesis
Through the use of a variety of well-planned tests, the stability of Pencil code after any modification can be
proven by running a single command. This will make for easy code transitions and confident developers.
6.3 Methods
The Pencil group seeks to run tests in parallel with each of three design phases. The tests are cumulative,
so that each phase will integrate the tests in all phases previous to it with its tests.
6.3.1 Phase I
Phase I of the testing process will correlate with the initial development phase, where the compiler does
not work from beginning to end, but the essential components are being built and then gradually linked
together.
For this phase, we will use white box testing, so called for its detailed examination of each Java class
that is written. We will test each method in each class to ensure that they perform as expected.
6.3.2 Phase II
Phase II of the testing process will correlate with the finessing phase of development. At this point, all the
major classes have been written and pieced together in their final arrangement.
Tests for this phase will be less minute. We want to ensure that all the blocks of code are correctly
interacting. Does the compiler behave as expected on known inputs?
This is the phase where regression tests become important. As inconsistencies are discovered and corrected
throughout the code, it is imperative that the flow of data is not affected. With one person modifying another
person’s code, it is easy to inadvertently break functionality.
Further, as our compiler begins to accept more complete grammars, we do not want to lose functionality
on the initial grammars that were parsed correctly.
22
6.3.3 Phase III
Phase III is the final testing phase, entered when development is finished. By this point, all of the white box
tests and regression tests have been passed and the code is ready for production.
Installation tests will be performed to ensure that the users can install the compiler with minimal difficulty.
Black box tests will be performed on large Petri nets gathered from real-world examples, to ensure that the
compiler performs as expected for varied useful inputs. If necessary, the compiler will be modified as a result
of these tests.
6.4 Tools
JUnit will be used to structure the Phase I and II tests performed against the Pencil compiler. Perl and
shell scripts will be used as necessary.
6.5 Implementation
6.5.1 Phase I
Phase I seeks to test each method in each class to ensure that it performs as expected. It will not test get()
and set() methods except where deemed necessary.
Here is a brief overview of the Pencil classes and the structure that they will fit into:
1. Syntax analysis - Pencil uses the JFlex Lexer and the CUP parser to generate syntax analysis classes
for its code. To test the parser and lexer generated by this code, we will write a test method that
submits a variety of grammars to the parser as a regression test. By running correct and incorrect
Pencil spec files through the compiler, we will seek to keep parsing consistent through the development
process. As more functionality is added, more Pencil spec files will be written and added to this test.
2. Director/Builder - The director takes the Syntax Tree created by the parser and called Builder methods
on it. A test method will be created to perform regression tests on each of the Director and Builder
methods.
There are many small object classes written for the Director and the Builder. A series of small tests
will be written to test most of these classes as well.
3. Runtime - The outputted code format. Because we are outputting to Java files, it is important that
there are no bugs in our runtime code either. A series of test methods will ensure that the Java
framework works under different Petri Net inputs.
In order to complete Phase I, a list of each class and its methods must be compiled. The methods must
be examined for expected behavior, and test code must be produced to ensure that expected behavior holds
true. The tests will be tied together using JUnit and run at least once daily.
6.5.2 Phase II
Given a working compiler, does an input .pen file produce expected Java code? Does it do so every day, as
changes are performed? For this phase, test methods must be written to test all aspects of the grammar.
Care must be taken to overcome the developer’s bias and test unexpected input. Equally important is that
invalid input is always rejected, regardless of how advanced the error handling implementation is in the code.
In this phase, we begin to write tests that test the flow of data between classes.
23
6.5.3 Phase III
Because time ran short in the developement process, most of the time at the end of the project was spent
writing documentation and last-minute code changes. This means that the final testing phase became one
of trial and error. The developers made changes and tested them in the most logical fashion. This is not
desirable because each programmer has his or her own bias as to how the code should perform, and is
programming towards that goal. Independent testing is the only way to ensure reliable code. Given another
week, this independent testing of installation on different platforms, and of pencil spec files written by other
scientists would have guaranteed that the code was resiliant.
24
Appendix A
Pencil Grammar
The following lists all of the grammar productions described in Sections 3.2 and 3.3. The start production
is PencilSpecification.
25
P laceDeclaration → ‘place’ P lace (‘,’ P lace) ∗ ‘;’
| ‘place’ P lace P laceDef inition
P laceDef inition → ‘{’P laceAttribute ∗ ‘}’
P laceInputs → ‘in’ ‘:’ T ransitionList ‘;’
P laceList → P laceArc (‘,’ P laceArc)∗
P laceN ame → Identif ier
P laceOutputs → ‘out’ ‘:’ T ransitionList ‘;’
P laceP arams → ‘(’ InitialM arking ‘)’
‘(’ U pperBound ‘)’
‘(’ InitialM arking ‘,’ U pperBound ‘)’
‘(’ U pperBound ‘,’ InitialM arking ‘)’
Qualif iedIdentif er → Identif ier (‘.’ Identif ier)∗
ReturnV alue → ‘[’ ‘ret’ Expression ‘]’
T ransition → Identif ier
T ransitionArc → T ransitionN ame ArcW eight?
T ransitionAttribute → T ransitionInputs | T ransitionOutputs | F iringRule
T ransitionDeclaration → ‘transition’ T ransition (‘,’ T ransition) ∗ ‘;’
| ‘transition’ T ransition T ransitionDef inition
T ransitionDef inition → ReturnV alue? ‘{’ T ransitionAttribute + F iringRule? JavaBlock? ‘}’
T ransitionInputs → ‘in’ ‘:’ P laceList ‘;’
T ransitionList → T ransitionArc (‘,’ T ransitionArc)∗
T ransitionN ame → Identif ier
T ransitionOutputs → ‘out’ ‘:’ P laceList ‘;’
U pperBound → IntegerExpression
V ariable → Identif ier
26
Appendix B
B.1 Introduction
The purpose of this document is to provide basic standards for collaborative code development. Programmers
can be very sensitive about where they put their curly braces and semi-colons; it is not the goal of this
document to put any team member in a straight-jacket with respect to his or her idioms and idiosyncrasies.
The guidelines contained herein reflect core best practices in pursuit of making one’s code perspicuous and
maintainable. Team members should try to follow these guidelines as much as possible in their work.
B.3 Tabs
Code files should not contain hard tab characters. A file with hard tabs may appear fine in your text editor
and appear as a convoluted mess in someone else’s. This is because editor’s are free to define tab stops
as they please. All tabs should be converted to spaces when a file is saved. To do this in emacs, use the
command ‘(setq indent-tabs-mode nil)’. In vim, use the command ‘set expandtab’. If you do not use
emacs or vim, you should check your software documentation, then reconsider your choice of editor.
27
Bibliography
[1] Tadao Murata. Petri nets: Properties, analysis and applications. Proceedings of the IEEE, 77(4):541–580,
April 1989.
[2] James L. Peterson. Petri Net Theory and the Modeling of Systems. Prentice–Hall, Englewood Cliffs,
N.J., 1981.
[3] Carl Adam Petri. Kommunikation mit Automaten. PhD thesis, Bonn: Institut fuer Instrumentelle
Mathematik, 1962.
[4] C. Sibertin-Blanc. CoOperative Objects: Principles, Use and Implementation. 1998.
28