A report that presents Espresso, a Java compiler developed at BU during a directed study in advanced compiler design. The main goal is to introduce the basic concepts and the architecture of Espresso, in order to provide a basis for future work.
Espresso, a Java compiler written in Java is presented. It was developed in the course of a directed study in advanced compiler design over a period of about 3 months under the supervision of Prof. A. Kfoury. The main contributors to design and implementation up to the current version 0.3 are Santiago M. Pericas ( email: email@example.com ) and Karl Doerig ( email: firstname.lastname@example.org )
Espresso's main purpose is to serve as a workbench for students interested in applying their theoretical knowledge from programming languages and compiler design and construction in the context of a compiler for a modern object oriented programming language.
Java, an object oriented programming language developed at Sun Micro Systems was first released in 1996. It quickly became one of the most widely used programming language in industry, in particular for the client side programming of web based internet and intranet applications.
The strength of Java lies in a carefully designed language that includes most of the benefits provided by C++, while at the same time being more compact and consistent in its design. Among others, one major reason for its success was the standardization of the Java compiler and the portability of compiled class files across platforms and operating systems.
Espresso, a Java compiler developed at Boston University in a directed study project, provides a compiler for a state of the art object oriented programming language to students who are interested to turn their theoretical knowledge from graduate level programming language and compiler design and construction courses into practical application and improvement of a modern programming language compiler. Because Espresso is an open project and contributions are highly encouraged.
Section 2 first presents the overall architecture of Espresso. It then introduces to design and concepts of the abstract syntax tree AST, the SymbolTable , and the JavaImportManager , a component responsible for looking up and loading compiled Java classes into Espresso's environment.
Section 4 introduces to type checking and highlights key concepts around which type checking is built up. It includes a detailed description of the type hierarchy and introduces to an interesting metric discovered by Santiago M. Pericas. This metric is central during type checking, because it allows to deal with the various situations involving primitive types, reference types and method types in a uniform way.
Section 6 contains a collection of proposals for future work. It first contains a list of Java language features that are currently only partly type checked, and/or for which code generation is missing or incomplete. It also points out possible improvements of the generated code by applying appropriate optimization techniques. Finally it makes some suggestions to improve error reporting.
Most of the implementation oriented objectives have been reached. A first version of Espresso was available for testing and performance evaluation after about 3 month of hard work in May 1998. During summer and fall 1998 further enhancements have been made to both performance and functionality. Espresso's most recent version in v0.3 For a list of missing or incomplete features in Espresso along with suggestions for optimizations and improvements see Proposals for future work .
Espresso is built using a compiler construction tool called JavaCC from SunSoft (trademark Sun Microsystems). Given a JavaCC grammar file as input, JavaCC constructs a LL(k) parser along with a token manager implementing the lexer for the language to be compiled.
The generated Java parser is used to parse the Java source code. During this compilation step, an abstract syntax tree is constructed which forms the basis for the type checking and code generation phase.
A complete set of documentation for JavaCC is available through the JavaCC homepage http://www.metamata.com/javacc/DOC . The Espresso home page also contains some introductory slides to basic concepts of JavaCC .
The package JavaClass used to read and write Java class files, and the package ClassGen used during code generation were downloaded from Markus Dahm home page at http://www.inf.fu-berlin.de/~dahm/JavaClass/ and adapted to our needs.
The UML class diagram in Figure 1 shows the top level structure of Espresso, including the root of the abstract syntax tree.
The UML sequence diagram in Figure 2 shows an overall picture of the order, in which Espresso goes through the different phases of compiling.
On the root node of the AST (an object of type CompilationUnitNode ), Espresso invokes the typeCheck method. Each node propagates the typeCheck call down the AST according to its own type checking logic.
If no error occurred during the type checking phase, Espresso calls the translate method on the root node of the AST which triggers the generation of code for the Java Virtual Machine JVM . Again, each node propagates the t ranslate call down the AST if required by its translation rules.
During the parsing step of Espresso, an AST is constructed. The nodes of the AST are instances of classes which are part of a large class hierarchy with the class SyntaxTreeNode as its common base class. All AST nodes dealing with the various types of expressions share the common base class ExpressionNode . Similarily, nodes representing the different types of statements share the common base class StatementNode .
A detailed documentation of the class hierarchy is available through the Espresso home page ( see javadoc ). A graphical representation of the class hierarchy is available through the SGI Java Development Environment CosmoCode .
With the nodes of the AST being related in a class hierarchy, methods for type checking, evaluating expressions and translating can be defined as abstract methods at the appropriate levels, and concrete implementations can be provided. Using this approach, common functionality of related classes can be factored out and appropriate code can be placed in common ancestors in the class hierarchy.
Espresso's symbol table is implemented as a flat data structure using a hashtable. In order to deal with the different scopes, a hierarchical key concept was developed. The key entries in the symbol table are represented by objects of type Symbol , a class that provides methods to construct symbols for the different entities, and to retrieve their parts.
Table 1 shows the entities currently maintained in the symbol table, along with the symbol encoding scheme for each entity.
For each of this entities, the symbol table provides an add and a l ookup method. The reason for having entities dealing with primitive operators will be discussed in Section 4.3 ..
During the parsing phase of Espresso, appropriate entities are added to the symbol table. This includes entities appearing in the compiled unit as well as relevant entities (type declarations, method declaration, field declarations) which needed to be imported from previously compiled Java class files.
Instead of providing new classes to contain relevant information associated with each type of entity in the symbol table, each entry points back to the node(s) in the in the AST where it was created, or where relevant data associated with it can be retrieved.
Table 2 shows the different entities of the symbol table and the objects that are stored along with them.
Vector 1 of MethodDeclarationNode
Vector 2 of MethodType
Figure 3 shows a small compilation unit along with the resulting AST and with the entries created in the symbol table.
Note, that for entries of the symbol table created by the JavaImportManager (marked JIM in rightmost column), appropriate nodes for the AST are also created but not shown. The AST nodes marked 6 to 9 have triggered loading of classes java.lang.System and java.io.PrintStream and as a result entries 6 to 9 were added to the symboltable.
Each Java class file contains compiled code for exactly one class or interface. The exact format of a Java class file is part of a standard defined by
and is available online at
This standard quarantees that compiled Java classes are portable across platforms and operating systems.
JIM fully supports all currently defined formats. To accomplish the rather tedious task of reading the content of class files and of constructing the corresponding constant pool , JIM uses a package called classgen which is available to the public under the GNU licence agreement.
This resource is a vector of locations in the host file system at which Java class files can be lookup up. This locations are either root directories of packages or path descriptions to class files which are grouped together in either Zip files or Jar files.
Part of the content of the CLASSPATH vector is predefined by resource definitions set up by the JDK installation procedure. Additional definitions can be added by the user through the CLASSPATH environment variable.
If during compilation, a simple name possibly representing a type name is encountered, JIM has to determine whether this simple name can be matched with a Java class file name. To accomplish this task it has to build the cross product between the entries in the CLASSPATH vector and the entries in the import on demand list, then patch the simple name to each of them and try to look it up. If a lookup was successful, the concatenation of the simple name and the appropriate portion of the import on demand entry represents the fully qualified name for the type. In addition, the JLS requires that a simple name never refers to a type name that is present in more than one package. JIM makes sure that a simple name can be qualified uniquely and issues an error otherwise.
Espresso's parser JavaParser is constructed by JavaCC from the JavaCC grammar file java1.0.2.jj, which contains the code specifying the lexical definitions and the grammar productions for the language. Grammar productions are augmented by semantic actions to manage and execute the construction of the AST and to control and update the environment as parsing goes along.
General concepts of LL(k) parsing are not discussed here and are available through textbooks on compiler construction. For details on the JavaCC grammar and the general environment of JavaCC read the mini tutorial on JavaCC available through the JavaCC home page..
There is a field className_d . which at any point during parsing contains the fully qualified name of the currently parsed type (class/interface). Because a Java compilation unit may contain the definition of more than one type, this field needs to be updated each time a new type declaration is seen.
During parsing, the scoping rules for classes, methods, fields, formal parameters and local variables have to be enforced. For classes, methods and fields, the scheme using package name , class name and field-/method name suffices to provide for the full flexibility defined by the language.
Espresso solution to this problem is to provide a scope stack, a data structure which is maintained in the JavaParser field scopes_d . The scope stack is implemented as a stack containing instances of Integer objects.
Names of formal parameters and local variables appearing within a particular scope receive the current top of the scope stack as their scope identifier. This identifier along with the fully qualified method name and the name of the variable itself suffices to uniquely identify a local variable or a formal parameter.
In order to ease code generation and to avoid passing around break lists and continue lists (containing branch instructions to be backpatched) across statement and block boundaries, a number of control statements are wrapped with labeled statements during parsing. The list of control statements and expression lists currently wrapped by the parser includes those, which either potentially contain break and/or continue statements, or for which a special handling for the continue statement is needed.
If the statements listed above are not already labeled in the source program, the parser pushes a fresh label onto the label stack maintained in JavaParser field label_d and wraps the statement with a labeled statement using this newly created label. For the update clause of the for statement, a new label is always created. If during parsing of the above constructs, a break or continue statement is seen and the optional label associated with the statements is not present in the source program, than the parser assigns the current top of the label stack as the label to it.
Therefore, whenever the end of a class declaration is reached, the parser has to verify whether at least one constructor was defined, and add a default constructor to the class body otherwise. This default constructor needs to call of the default constructor of the superclass of the class.
Non static fields of a class (instance variables) are initialized by the JVM with a default value whenever a new object of this type is created. If the source program defines an initializer for the field, this initializers has to be used instead. The solution proposed by the JLS is to add all initializers in textual order to each constructor, by turning them into assignments to the appropriate fields.
To implement this solution in Espresso, JavaParser maintains a special field fieldInit_d representing a block, to which field initializers are added whenever they are encountered, and after constructing an assignment expression for it.
After verifying for the presence of at least one constructor in the class (at the end of ClassDeclaration() ), the block containing these initializers is added to each constructor found in the body of the class.
If the programmer provides initializers for static fields, or if special static initializer blocks are defined, then a special static constructor <clinit> needs to be added to the class body by the compiler.
Whenever the parser encounters a declaration of a static field with an initializer, it turns the initializer into an assignment to that field, and adds it to the JavaParser field staticInit_d . In a similar way, statement expressions found in static initializer blocks are added to the same field, always taking the textual order into account.
At the end of parsing a class declaration , the parser adds a static constructor to the class body (provided staticInit_d is not empty).and adds statements and statement expressions contained in static_init_d to the block of the static constructor.
As a last step of the parsing phase, we need to verify whether all references to types (classes/interfaces), methods and fields appearing in the source code have been uniquely matched with either declarations found in the compiled unit itself or with classes loaded by JIM.
If type names appearing in type expressions, this expressions and super expressions were not resolvable during parsing, the corresponding symbols are added to a special JavaParser field forwardTypes_d .
After an AST was successfully constructed by JavaParser , type checking is performed on the nodes of the AST. Each node of the AST provides a typeCheck method that is responsible to perform the type checking of the language construct is represents and to propagate type checking do its successors in the AST if required.
During parsing, types declared for methods, fields, formal parameters and local variables are collected by the parser, and turned into instance of classes of the type hierarchy. Constructing type instances also includes to encode these types according to the encoding scheme defined by the JVM to make them comparable to the ones loaded from class files.
The main tasks of type checking is to determine the resulting types of expressions, to verify, that access restriction for packages, classes, methods and fields are respected, and in general, to enforce the typing rules imposed by the JLS.
The type hierarchy developed for Espresso is among the most important and central concepts of Espresso. Almost all non trivial functions for type checking are integrated in this hierarchy. It also plays a major role in supporting code generation.
Espresso's type hierarchy also contains a special type for methods . Although the Java language has no objects of type method , the availability of this special type proved to provide an elegant solution for many problems during type checking and code generation.
The UML class diagram in Figure 4 shows Espresso's type hierarchy in detail.
A method type is identical to another type, if the other type is also a method type, and moreover, if the return types, and the types of the formal parameters are identical to each other. (There is also a version that ignores the return type: equality modulo return type ).
The typing rules imposed by JLS require a compiler to choose the most specific method among all the methods that are applicable for a particular method invocation. Moreover, the JLS also defines a host of conventions a compiler has to deal with, when type checking of expressions involving primitive operators is performed. This includes, which primitive types are assignable to each other, and what the result type of a unary or a binary primitive operator is, taking the types of the operands into consideration.
In the course of developing Espresso, Santiago M. Pericas discovered a central metric that allowed to cover the different requirements with a single concept, a distance function. The general idea is to define quantities bigger than or equal to zero for operations which are allowed between certain types, and to define negative quantities for operations which are not applicable between certain pairs of types.
During checking, the smallest quantity bigger than or equal to 0 is the one that needs to be chosen by the type checker. If no such smallest positive quantity was found for a particular case, then that case was not typeable.
The matrix in Table 4 documents the distanceTo function for all types of the type hierarchy.
The distance between two method types ( indicated by in the matrix above ) is defined as the sum of the distances between the individual arguments of the two methods. More formally, let S =( s1x .. xsn) and T =( t1x .. xtn) be two method types and let be the distance function. Then the distance ( S,T ) is defined as:
The JLS defines pages and pages of rules that need to be enforced when applying primitive operators on primitive types, including conditions, under which widening or narrowing of primitive types has to take place.
Espresso's type checking implements all this rules in a uniform and very elegant way. For all the unary and binary operators on primitive types, an initial environment is added to the symbol table.This environment defines for each unary primitive operator, for which primitive type it is defined, and what the resulting primitive type for the operation is. This is done by adding the primitive operator to the symbol table, along with a method type for each possible type supported by the operator describing the type of the argument and the result type.
Binary operators on primitive types are defined in a similar way by providing a method type with two arguments and a result type.
Note that the order in which these method types are added to the symbol table for a particular operator directly affects the desired outcome of a lookup.
To type check an expression involving a primitive operator we only need to determine the types of its arguments, create an appropriate method type, and use the definitions given in the initial environment together with the distanceTo function presented above, to determine whether the expression type checks and what the result type is.
Espresso's implementation of the type for class types ( ClassType ) provides a method findMethod , to determine the most specific method declaration for a particular method invocation expression ( MethodExpNode ) as defined in JLS 18.104.22.168
Type checking of MethodExpNode first sets up an instance of class MethodDesc, which contains a method type describing the signature of the method invocation expression, the name of the method and some other fields that are used to keep track of additional information that is collected during the search.
, the symbol table is looked up for matching methods using the name of the type and the method name that was initially provided. Methods obtained from the symbol table with a matching number of arguments are sorted out, and among those, the most specific is chosen by applying the
function of the initial method type to the type of each candidate method. Once the most specific candidate method declaration of a particular type (class/interface) is determined, the search continues recursively up to that types supertype.
The search either stops when an ambiguity among candidate method declarations was encountered ( throws a type check error ), or when Java's primordial type Object was reached. If searching was successful, the most specific method is returned to the initial caller.
During the search up the type inheritance hierarchy, findMethod also takes care of requesting JIM to load required class files in order to make the appropriate type information available in the symbol table.
Espresso's ClassType also provides a method for finding fields in order to type check field expressions. Unlike for finding methods, finding the appropriate field declaration not only needs to check in superclasses of a particular type, but in its superinterfaces as well. The technique applied for this search is similar to the one used for finding methods.
The JLS defines special rules, that apply to initializers of variables.While in normal assignments an integer constant is always of type int, and the programmer needs to explicitly cast the type down when the RHS of the assignment is an integral type smaller than int, initializers of variables need to be treated differently.
In fact, the JLS requires, that initializers of variables are always evaluated. If the result of the evaluation is a constant expression, then the compiler needs to verify, whether the value of the constant expression fits into the primitive type that needs to be initialized.
The expression hierarchy of the AST provides a special evaluation function for each kind of expression which possibly evaluates to a constant expression. The expression base class ExpressionNode defines a method evaluate . Each subclass of ExpressionNode that potentially evaluates to a constant expression overwrites this method and invokes the evaluate method of its arguments. If no exception was thrown, the appropriate operation is performed ( Ex: addition or subtraction in AndExpNode) on the constant expressions resulting from its arguments and handed up the expression tree in the AST. The ExpressionNode then provides a second method evaluateExp that can be called in the type checking phase, which either catches an exception and returns null indicating, that the initializer did not evaluate to a constant expression, or returns the evaluated value, which is used by the type checker to verify, whether it fits into the type of the initialized variable.
The main class Espresso initiates the translation phase by invoking the translate method of the root node of the AST. The different nodes in the AST propagate the translation calls down to their children as needed.
Espresso uses the package classgen , which is available under GNU's licence agreement. This package provides a complete infrastructure to build up a constant pool , from which at the end of the compilation, the content of a class file can be retrieved. It also includes a complete set of classes representing the instruction set of the JVM.
This section first introduces to the important classes and methods of the classgen package. Then it presents the translation step in the TypeDeclarationNode , which initiates the translation of methods and is responsible for adding them to them Constant Pool.
Generating code for a particular type declaration starts by creating an instance of
, the top level class of the package
, which is used to manage the construction of the Java class file. It provides methods to add the components of the class body and to access the class files constant pool directly if needed.
To construct an instance of ClassGen , the name of the Java source file, the type name, the name of the supertype, the types access flags and the list of interfaces that are extended or implemented by the type need to be supplied.
Translation proceeds by adding appropriate code for the entities of the class body.
Generating code for interface declarations mainly consists of adding entries for field declarations and method declarations to the constant pool.
Generating code for class declarations requires to translate the Java code contained in the method body, and is discussed in more detail in this section.
After completing translation of all entities of the class body, the content of the generated Java class is retrieved from classgen , obtaining an instance of class JavaClass (contained in package javaclass), which provides a method to dump its content to a Java class file.
For each method declaration with a non empty body, an instance of classgen.MethodGen needs to be created. We need to supply the methods access flags, the encoding of the return type and the types of the arguments together with the argument names ( become local variables ), the class and method name, and finally an instance of classgen.InstructionList to hold the byte code instruction stream.
For nodes of the AST representing expressions or statements, the translate method has a uniform signature, taking the current java class and the currently translated method as parameters. This two objects suffice for most cases to provide access to the java class and to the code that was generated so far.
After code generation for the method body is completed, NOP's are removed from the instruction list, and the translated method is added to the java class. Because of the way the package classgen is constructed, this needs to be done as the last step in translating a method body.
Beyond type checking, Espresso's type hierarchy is also heavily used during translation. The JVM instruction set distinguishes operand types by providing distinct opcodes for operations on its various data types. The first distinction is made between reference types ( class type, array type, null type ) and primitive types. Looking in more detail at the instruction set of the JVM, the following instruction families can be made out:
The primitive type boolean is always treated as type int . For most operations on the Java types byte, char and short , no special instructions are available. Instead a set of instructions is provided to convert forth and back between those types and the type int.
In order to minimize data type specific case analysis in the nodes of the AST during translation, Espresso's type hierarchy provides a set of methods to construct appropriate instructions depending on the type of the operand. Instructions constructed this way include:
To illustrate the principle, consider the case of translating the return statement. Espresso's top type Type declares an abstract method RETURN . Table 5 shows the different types of return instructions which need to be created, along with the subclass of Type which provides the appropriate implementation:
One of the problems that needs to be solved when building a compiler for an imperative language is to make sure, that operations that change the state of variables are performed only on variables that represent updateable locations in memory. In the grammar used for Espresso, these updateable locations are represented by instances of the following classes of the AST .
Generating code for these expressions requires to translate an expression according to its use as either a right value rvalue (read current content of location) or as a left value lvalue (write to location).
To control the translation of such an expression, the base class of all expressions ExpressionNode provides a flag leftValue_d which is turned on or off either already in the parsing phase, or when appropriate, later in the translation phase.
A number of expressions require, that code needs to be generated for a lvalue and for a rvalue . Among these expressions are field expressions in pre-/postIncrement expressions and pre-/postDecrement expressions, depending on the context of their use.
Figure 5 shows the relevant part of the AST , that results from parsing the code fragment containing the assignment b = --u==v++
Because the JVM is a stack based machine, translating code for it corresponds to a post order traversal of the AST. This is reflected by the numbering of the nodes above We will use the resulting code shown in Table 6 , to walk through the translation.
Starting with Espresso v0.3 the translation of boolean expressions first tries to evaluate boolean expressions to obtain a constant expression . If not applicable, lazy evaluation of boolean expressions needs to be performed. To provide the necessary infrastructure for backpatching, the AST expression base class ExpressionNode defines a field trueList_d (for the true cases) to hold branch instructions created during translation, which need to be backpatched when the target of the branch instruction is known. Similarly, a field falseList_d is provided for the false case. As translation of boolean expressions proceeds, trueList_d and and falseList_d are passed up the expression tree and either merged or swapped according to the semantic of the particular expression. In most cases, backpatching is performed in the course of translating the enclosing control statement. Backpatching also needs to be performed, when boolean expressions need to be synthesized. This has to be done for instance in the following situations:
In order to synthesize boolean expressions, the class ExpressionNode provides a method translateSynthesized that performs the required task. Subclasses of ExpressionNode override this method if they require a more specific implementation.
In order to produce more compact code, Espresso translates boolean operators in a not obvious manner. All boolean operators found in the source code are translated to their negation. The true case always falls through, and the false case produced is added to the falseList to be packpatched later. The instruction at offset 23 in Table 6 is an example of this strategy, where an equality expression produces an if_icmpne.
Given this setup, translateSynthesized basically performs a regular translate on its parts, and then adds the appropriate code to leave the constant one (iconst_1) on the operand stack and appends an unconditional branch right after to the instruction stream. This instruction handle is returned to the caller of the synthesized translation at the end. Then it appends the constant zero (inconst_0) to the instruction stream, and backpatches the branch instructions contained in the falselist to this location.
We will use the following code fragment and parts of the resulting AST shown in Figure 6 to illustrate this principle in more detail. The example shows lazy evaluation of boolean expression, Espresso's principle of translating boolean operators to their negation, and the process of synthesizing a boolean expression for the purpose of performing an assignment.
Table 7 shows the code that was generated for the example above, along with remarks to particular translation steps.
This is the synthesized translation in e. Since the true case falls through, we need to push one to the operand stack. Then we need to add an unconditional branch to jump over the instruction which synthesizes the false case (This branch is returned to AssignmentNode to be backpatched, when the assignment instruction is added. Next we synthesize for the false case by pushing zero to the operand stack) and backpatch the merged falselists of e11 and e21 to this location.
A continue statement may occur only within one of the iteration statement do, while and for. Control is transferred to the continue target , which is either the innermost enclosing iteration statement in case no continue label was provided by the programmer, or to the labeled iteration statement that matches with the label of the continue statement . The current iteration is immediately terminated, and the next one is started.
As was pointed out in Wrapping of control statements , Espresso makes sure, that each continue and break statement is labeled, and therefore matching with the appropriate continue target is relatively easy.
The translation of a continue statement needs to add an unconditional branch to the instruction stream. In order to avoid passing around the instruction which represents the continue target, continue branch instructions belonging to a particular continue target are collected during code generation in the field continuelist of the appropriate enclosing labeled statement ( instance of LabeledStatementNode) .
When translating a labeled statemen t , first a NOP instruction representing the most likely target of a potentially contained continue is added to the instruction stream. Then translation of the enclosed statement is performed. At the end, branch instructions added by enclosed continue statements to the continuelist need to be back patched with the continue target . In the general case, this target is the NOP instruction that was first created. If however the wrapped statement represents a for statement with an non empty update expression list, then the start of that expression list represents the correct continue target according to the semantic of the language. The labeled statement that encloses that expression list can be looked up in the symbol table using the label symbol that results from adding the string "for" to the label contained in labeled statement that wraps the for statement.
Translation of the break statement works similar to translating the continue statement. The branch instruction created by the translation of the break statement are added to the breaklist of the appropriate labeled statement. After finishing translation of the enclosed statement in a labeled statement, a NOP instruction is added to the instruction stream, to serve as the target of the branch instructions contained in the breaklist.
In some cases, Espresso simply overwrites access modifiers that are potentially present in the source program by setting default access modifiers instead (
field- and method declarations in interfaces) JLS Ch. 9.3 and 9.4.
The reason for this is, that the same grammar productions are currently used for classes and interfaces.
Check for illegal mention of instance variables or instance methods declared in this class or any superclass, as well as for the use of this or super in any expression contained within an explicit constructor invocation statement.
Consequent evaluation of expressions throughout the translation process, so that constant expressions get transformed into single load instructions. This can be done during the code generation phase, by providing and applying the evaluation function in all expressions.
Among the language constructs which are currently not available, type checking and code generation for declaring, throwing and catching exceptions are the most important to be added first. This will allow us to compile a much larger number of Java programs with Espresso.
The following section describes the procedure you have to follow, in order to get access to the Espresso source code, and what changes you have to make to your environment before fetching the sources from CVS. It also establishes a few basic rules you have to stick to, when updating CVS with modified versions of Espresso source files.
Currently the Espresso source code is maintained on a local machine named
using CVS. This machine is Santiago M. Pericas (
private machine. He has volunteered to play the role of a "supervisor" for Espresso.
Follow these steps to get involved:
If you want a particular version of Espresso for testing, proceed as above, but to checkout from CVS, use command: cvs checkout -r versionid espresso, where versionid is the name of the version you want to test. (Ask Santiago M. Pericas for available versions).
This section introduces to the project structure of Espresso, as it is defined in CVS. The structure is described in terms of the directory structure, which results, when the entire project is checked out from CVS.
Espresso was developed using Silicon Graphics Java Development Environment CosmoCode. The use of CosmoCode is optional, but highly recommended. In order to use CosmoCode , an appropriate project file defining the project structure needs to be defined. A template project file named Espresso.pro j is managed within CVS and checked out to directory espresso. You will need to edit the file paths, in order to use it. Table 11 shows Espresso's directory structure together with the groups of files, which are located there.