User's Guide


(Current as of 21 Feb 2007) 

This document provides guidance on using SAFARI to define a customized IDE for a new language.  It addresses the following topics:

The sections that are relevant to all SAFARI users are the prerequisites, identifying a project for the language, creating a language description, and creating a parsing service, and using you SAFARI IDE.  The other services indicated are more or less optional, depending on the requirements of the IDE being bult.  Additional SAFARI services are under development and will be addressed here when they are ready for general use.

The discussion of the parsing service mainly addresses the case in which LPG (formerly and still occasionally known as JikesPG) will be used to define the language grammar and generate a lexer and parser.  However, other approaches to grammar specification and parser development can be accommodated in SAFARI, and the treatment of most other topics here is independent of the manner in which the parser is provided.

A note on procedure:  The steps through "creating a parsing service" are required for any SAFARI IDE.  The subsequent steps may mostly be pursued in any order.  Constraints on the ordering of particular steps are noted where applicable.  

A note on terminology:  The text below will refer to several of the classes generated by SAFARI by simplified "generic" names, such as the "Plugin" class or the "ParseController" class.  As these classes are actually generated, their names will typically be prefixed by the name of the language to which they apply.  Thus, if your language is called "MyLanguage", SAFARI will generate classes for you that are called "MyLanguagePlugin", "MyLanguageParseController", and so on.

Prerequisites

SAFARI runs on Eclipse 3.1.2; we are working on adapting it to Eclipse 3.2.  (SAFARI may also run on earlier 3.1 releases. It will almost certainly not (ever) run on any prior version of Eclipse.)

SAFARI now requires a Java 5 VM, due to a dependency on the Java Concurrency Utilities within the x10.runtime project.

Parts of SAFARI depend on Polyglot, currently version 2.1.0, which can be downloaded from http://www.cs.cornell.edu/projects/polyglot/.

You must have the SAFARI features installed in your Eclipse workspace.  As of 06 Feb 2007 the latest SAFARI release is number 1.0.45.  SAFARI is distributed as four features:
  • SAFARI (feature id org.eclipse.safari)
  • SAFARI Runtime (feature id org.eclipse.safari.runtime)
  • JikesPG IDE (feature id org.eclipse.safari.jikespg)
  • X10 Development Toolkit (feature id org.eclipse.safari.x10dt)
The first three are needed for developing or running SAFARI IDEs in general.  The last is relevant if you're interested in working on or with the X10 language.  Both the JikesPG IDE and X10 Development Toolkit provide examples of IDEs built using SAFARI.

For information on installing SAFARI, please look at the installation guide (which also lists SAFARI prerequisites).

Identifying a project for the language

The goal of this step is to set up an Eclipse project for the development of SAFARI IDE for your language.

A SAFARI IDE must be defined in a Eclipse plugin project.  Each SAFARI IDE must be defined in a separate project.  If you do not have a suitable plugin project available as a home for the new IDE then you should create one.  (A simple plugin project is all that's required.)

When you use the Eclipse New Project wizard to create a new plugin project, the wizard by default creates a package with the name of the project.  In that package the wizard creates a plugin class with a name of the form "<ProjectName>Plugin.java."  The Safari New Programming Language wizard likewise creates a new package and plugin class, but one that by default is named based on the programming language rather than on the project.  If you use the default values in each wizard, you can end up with two plugin classes, each in a different package.

There are two ways to avoid this.  One is just to give your project and language the same name.  The other is, during plugin-project creation, to override the default values and give your plugin class and package a name that matches that of your language.  (With the latter approach it is possible to have the name of the language be different from the name of the project in which the language IDE is defined.)

(Note:  There is a SAFARI wizard called "New Project Wizard" but this is a wizard for creating project wizards, not a wizard for creating projects.)

Creating a language description

The goal of this step is to provide basic information about the identity and characteristics of the new language.

This step is accomplished entirely within a SAFARI wizard.  To run this wizard invoke:

"File" -> "New" -> "IDE Language Support" -> "Programming language descriptor"

A note on navigating these Eclipse menus:  Under the "New" menu you may need to select "Other ..." in order to get to the next item.  Instead of following the menus, you can type "Ctrl-N" to open the     "New--Select a wizard" dialog from which you may be able to select a desired item directly.  These comments apply to all of the menu-navigation instructions below.

In the "Programming Language" wizard, you must provide information for the following required fields:
  • Project:  Browse to or type-in the name of your language project
  • Language:  Enter the name of the language (which for now must be the same as the name of the project).  This is the canonical name for your language, by which it will be identified to various SAFARI services.  Within SAFARI it is case-insensitive.
  • Description:  The “description” field is for the entry of a user-readable, user-sensible description of the language.  Enter whatever description you think is appropriate.
  • Extensions:  The “extensions” field is for the entry of file-name extensions that may be used for source files in the language.  Enter a non-empty, comma-separated list of extensions (omitting dots, and without spaces).  The SAFARI services defined for your language will be available for files with these extensions.

You may also provide information in the following optional fields:

  • Synonyms:  This is a comma-separated list of alternative names for your language
  • DerivedFrom:  The “derivedFrom” field should be the canonical name of the programming language, if any, from which the new language is derived.  The "derived from" language, if any, should be one for which there is already a SAFARI IDE.  It is used within SAFARI for determining inheritance relationships when locating language services for a given language (for example, locating an appropriate editor if none is defined specifically for your language).  If your language is derived from another SAFARI-supported language then this field should be filled in accordingly.  If your language is not derived from another SAFARI-supported language then this field should be ignored.
  • Icon:  This should be the project-relative pathname of an icon that can be used in graphical interfaces to mark files in your language.
  • URL:  This is intended for the URL of a web page that contains information about your language.
  • Validator:  The “validator” field can be given the fully-qualified name of a class that can determine whether a given input file actually contains text in the given programming language.  This class must extend the LanguageValidator class

Hit “Finish” when you are done editing the various fields in the wizard.  A SAFARI language description extension will be created using the above information and added to the project’s plugin.xml file.  This step also updates the projects Plugin class (to extend SAFARIPluginBase, among other modifications), or it will create a Plugin class if one does not exist.

(In earlier versions of SAFARI, this wizard also generates a "preferences" package and some preferences-related classes.  In later versions this feature will probably be eliminated as a separate wizard has been provided for generating a preferences service and preference pages.)

Creating a grammar and parsing service

The goal of this step is to create a grammar for the language and a lexer and parser based on this grammar.   A SAFARI wizard generates skeleton files for the lexer and parser grammars, which then need to be completed by the user with specific language details.  The JikesPG builder can then automatically generate lexer, parser, and related classes (including AST node types) based on the grammar definitions.

To run the wizard to generate the grammar skeletons, invoke
"File" -> "New" -> "IDE Language Support" -> "Parser Services" -> "JikesPG Grammar and Parser for SAFARI".

 In the wizard:

  • Be sure that the proper names are entered into the project and language fields.  (These may be filled in automatically or you may have to set them manually.)  As these values are entered, reasonable default values will be automatically entered into other fields.

  • If you wish, edit the "class" field to specify a non-default fully-qualified name for the parse controller that will be generated by the wizard. This class is used to give access to the token stream, AST, and so on, to the various higher-level language services.  (Note:  A parse controller is necessary, so this field should be given a usable class name.)
  • For the "templates" field most users will want to use the default setting of "UIDE" (which indicates to JikesPG where it can find the grammar file templates)
  • For the "options", check or uncheck these according to your own needs:
    • "Language has keywords" should be checked or not according to whether your language has keywords; this will affect the generation of a keyword lexer
    • "Language requires backtracking" should be checked or not according to whether your language requires a backtracking parser; this will affect the ability of the generated pareser to backtrack
    • "JikesPG auto-generated AST classes" should be checked or not according to whether you want JikesPG to automatically generate AST classes for your language.  (This can be a big convenience.)
  • Hit "Finish" when done editing.

Finishing the wizard has the effect of adding a parser extension to the plugin.xml file in your language project.  The wizard also creates one or two packages in the source folder of the language project:

The “parser” package (typically called something like “languagename.safari.parser”) is always generated.  This package is initially populated with two class files and three grammar-files.  The grammar files include one “.g” file that contains the main parser grammar and two “.gi” (“grammar include”) files, one for a keyword lexer and one for a general lexer.  The classs are the ParseController, which is the class through which parse-related services and data are accessed by other SAFARI services, and the ASTNodeLocator, with which the parser (or other clients) can find nodes in an AST.   When the grammar files are instantiated, that should trigger the execution of the JikesPG builder, which is provided with  SAFARI, and that builder should generate a number of additional classes that provide particular parts of the lexer and pareser implementations on which the ParseController and ASTNodeLocator depend.  (Also see "Notes about compilation", below.)
The “AST” package (typically called something like “languagename.safari.parser.Ast”), if generated, will contain an interface for the each nonterminal in the grammar and a class for each production in the grammar.  It will also contain useful classes for representing AST nodes and AST visitors.  This package is generated by default when the the option "JikesPG auto-generated AST classes" is checked. 

(It is also possible, by configuring the JikesPG options differently, to have the AST-related types generated as members within the parser class.  This is controlled by the "automatic_ast" option within the ".g" file.  With the option given as "automatic_ast=toplevel" a separate AST package is generated.  If this option is changed to "automatic_ast=nested"--or just "automatic_ast"--    then the AST-related classes are generated as members of the parser class.  For this document we assume that the AST is represented in its own package.)

Additionally upon finishing the wizard, the JikesPG grammar file for your language will open in the editor, and the structure of the file should appear in the "Outline" view.

The grammar files contain some generally useful elements (including directives to JikesPG regarding generation) and some simple instructions for completing the files.  The files also include some example entries for a simple expression language.  You will need to edit these file as appropriate for your language.  A quickstart guide to JikesPG, as well as links to additional documentation on JikesPG, is found here.  As noted there, the JikesPG grammar files make ample provision for the inclusion of custom code in a generated parser.

The grammar files may be edited in any order.  However, for purposes of generation, the general lexer depends on the keyword lexer and the parser depends on the general lexer.

Your language project should be configured to include the JikesPG Grammar File Builder.  This should happen automatically when you run the parsing-service wizard.  If for some reason your language project is not configured to include the JikesPG builder, then you can enable the builder by selecting the "Enable JikesPG Builder" in the context menu for the project.  If for some reason you want to run JikesPG manually (which shouldn't usually be neecessary), you can find information about that here.

Notes about compilation

The generated parser classes will have a compilation dependency on the lpg plugin, which is provided as part of the SAFARI release (lpg is actually an open-source release of JikesPG).  When the Jikes PG grammar and parser wizard is run, the wizard adds a plugin dependency on lpg to your language project.  However, although the dependency on lpg should then appear in the plugin dependency list for your project, it may not actually be recognized.  As a result, the ParseController may not compile properly.  To cause the new dependency to be recognized, it seems that the plugin.xml file, or the dependencies within it, must be manipulated somehow.  For instance, if the plugin.xml file is open, you can go to the dependencies tab, select the lpg dependency, and move it up or down in the dependencies list, afterwards saving the file.  That should cause the dependency to be recognized and trigger a recompilation of the project, which should succeed.

If your grammar defines a type with a name that duplicates one of the Java type names (such as “Number”), then you will need to disambiguate references to this type in the parser (which can be done by explicitly importing the corresponding AST node types for your language).  The Java compiler should make any problem like this evident to you.

Other approaches


If you have your own parser:  Since the goal of this step is to produce a lexer and parser, if you already have a lexer and parser that meet SAFARI requirements then these may be used directly.  The "Hand-written Parser" wizard allows you to select an existing parser and creates a corresponding parser extension in the plugin.xml file.  To work within SAFARI, any "hand written" or non-JikesPG-generated parser should, at a minimum, implement the ILexer, IParser, and IParseController interfaces in the org.eclipse.uide.parser package in the org.eclipse.uide.runtime project.  This will entail providing implementations for several additional types defined by SAFARI or JikesPG.  (Some of these, unfortunately, are currently defined by concrete types, although we expect to provide interfaces for these in the future.)  Thus, at this time we recommend the use of JikesPG to generate parsers for use in SAFARI.  NOTE:  If you have a parser that was generated by JikesPG but outside of SAFARI, it should be relatively straightforward to adapt it to SAFARI.

If you have your own grammar:  If JikesPG is to be used to generate the lexer and parser, but you already have grammars for the language to be parsed, then these may be substituted into, or in place of, the SAFARI-generated templates.  Of course, the resulting grammar files must be compatible with JikesPG.

Creating a token-coloring service

The goal of this step is to create a class that will support coloring of tokens of different sorts when their text appears in the editor (for example, keyword highlighting).

SAFARI provides a wizard to generate a skeleton class for this purpose; this class must be specialized by the user according to details of the language and goals for the editor.  To run this wizard: invoke

"File" -> "New" -> "IDE Language Support" -> "Editor Services"  ->  "Token Colorer"

As before, in the wizard assure that the names of your project and language have been correctly entered into the appropriate fields.  If desired, edit the default name of the implementation class or its package. Hit "Finish" when done.

The Token Colorer wizard creates a safari.tokenColorer package containing a TokenColorer class.  The wizard adds a tokenColorer extension to the plugin.xml file for the language project and opens the TokenColorer class in an editor.

The TokenColorer class has two important methods:  the constructor, in which a set of TextAttribut's are created (which are later used to specify the text presentation for the various token kinds), and the getColoring(..) method, which returns the TextAttribute to be used for the given token kind.

As you can see, getColoring() calls IToken.getKind() to determine the token kind (represented by an integer) and uses that to compute the correct text attributes. The set of valid token kinds is defined by the lexical analyzer; they are typically located in the JikesPG-generated "Parsersym" interface (in the parser package) and generally start with a prefix like "TK_".

The generated TokenColorer class includes a few examples of text attributes and token colorings based on the example expression language introduced with the grammar-file templates.  These may not compile as generated (due to inconsistencies with the generated parser and AST) and will generally have to be tailored to the language being defined.

The generated TokenColorer class is somewhat dependent on JikesPG:

  • The TokenColorer class implements a Parsersym interface that is one of the standard, language-specific interfaces generated by JikesPG
  • The getColoring(..) method takes an instance of an IToken, which is defined in lpg.lpgjavaruntime
  • The getColoring(..) method also takes an instance of an IParseController; although this is defined by SAFARI, the default implementation makes use of several types generated by JikesPG
As noted elsewhere, it is possible to implement these various interfaces without using JikesPG (although it is the intention of SAFARI to relieve the developer of this work by automating the implementation using JikesPG).  

Creating an outlining service

The goal of this step is to create a service that provides an outline view of documents in your language.  As for the TokenColorer, there is a SAFARI wizard that creates a skeleton class that contains the generic structure of the outliner, and this must be tailored to create an outliner for the specific language being defined.  To run this wizard, invoke

"File" -> "New" -> "IDE Language Support" -> "Editor Services" -> "Outliner"

In the wizard, as before, assure that the names of your project and language are correct and, if desired, edit the default values provided for the implementation package and class.  Hit "Finish" when done.

The Outliner wizard creates a safari.outliner package containing an Outliner class (and an Images class).  The wizard also adds an outliner extension to the plugin.xml file for the language project and opens the Outliner class in an editor.

The generated Outliner class extends org.eclipse.uide.defaults.DefaultOutliner.  The principle method in DefaultOutliner is createOutlinePresentation(), which retrieves the current AST (from an IParseController) and then visits the AST, populating the outline tree with various TreeItems corresponding to AST nodes of interest.  The visiting is done by an instance of the OutlineVisitor class, which is a member of the generated Outliner class and extends the AbstractVisitor class.

The AbstractVisitor class contains default visit(..) and endVisit(..) methods for all node types in the AST.  These methods will traverse the AST but otherwise have no effect.  (The AbstractVisitor class is usually located in the “parser.Ast” package or—depending on parser-generator options—it may be contained as a member class within the generated parser class.)

To add a program entity to the outline, in the OutlineVisitor class simply override the default visit(..) method corresponding to that type of AST node.  Most overriding methods will call createSubItem(..) with an appropriate label.  The visit(..) method should return true (or false) depending on whether the children of the node should be visited (or not).  When an item label is created it is pushed onto a stack.  Thus, whenever a default visit method is overridden to create an item label, the corresponding endVisit(..) method should also be overridden in order to pop the item label from the stack.  Note that items that are inherently childless do not need to be pushed onto the stack, and if these items are not pushed onto the stack then it is unnecessary to override endVisit(..) for them.

Some example visit(..) methods for the simple expression language are shown in the generated Outliner.OutlineVisitor class (which may prevent the generated file from compiling).  These must be adapted for the specific language being defined.

It is necessary to have one visit(..) method that calls createTopItem(..) to create a root for the outline.  (It is also necessary to have a corresponding endVisit(..) method.)  This may be for some high-level node type (such as “program”) or it may be given a label that does not correspond to a specific node type.

The generated Outliner class is somewhat dependent on JikesPG:

  • The TokenColorer class implements a Parsersym interface that is one of the standard, language-specific interfaces generated by JikesPG
  • One of its central methods (to set the outline tree) makes use of IToken, which is defined in lpg.lpgjavaruntime
  • The createOutlinePresentation(..) method takes an instance of an IParseController which, although it is defined by SAFARI, has a default implementation that makes use of several types generated by JikesPG
As noted elsewhere, it is possible to implement these various interfaces without using JikesPG (although it is the intention of SAFARI to relieve the developer of this work by automating the implementation using JikesPG).

Creating a text-folding service

The goal of this step is to create a service that supports the folding of hierarchical syntactic elements in text editors for the language.  As with the outlining service, the SAFARI wizard for text folding creates a skeleton class that contains the generic structure to support text folding and this class must be tailored to the specifics of the language being defined.

 The SAFARI wizard that does this is called “NewFoldingUpdater.”  To run this wizard, invoke

"File" -> "New" -> "IDE Language Support" -> "Editor Services" -> "Source folding updater"

As before, in the wizard assure that the names of the project and language are correct and, if desired, edit the package and class name for the implementation. Hit "Finish" when done.

The NewFoldingUpdater wizard creates a safari.foldingUpdater package containing a FoldingUpdater class.  The wizard also adds a folding updater extension to the plugin.xml file for the language project and opens the FoldingUpdater class in an editor.

FoldingUpdater contains one main method, updateFoldingStructure(..).  This method obtains the current AST and sends it a new FoldingVisitor, which is implemented in the FoldingUpdater and extends AbstractVisitor).  The  should contain a visit method for each AST node type for which folding is desired.  Each visit method should simply call AbstractVisitor.makeAnnotation(..) with its given node.  (An example is shown in a comment in the generated FoldingUpdater code.)  The method makeAnnotation makes an annotation that spans the text represented by the node, and it is through the annotations that folding is enabled.  The language-specific customizations required for folding are simply to provide visit(..) methods for the AST node types for which text is to be foldable. These visit(..) methods should return true if folding should be allowed for nested units and false if not. It may be necessary to add more complicated logic if more precise control of folding is desired, for example, if you wish to enable folding of top-level blocks but not nested blocks.

Note:  The NewFoldingUpdater wizard requires the package and class names for the language AST node type.  The wizard currently assumes that the AST node type has the default name and is located in the default package.  If these assumptions are not valid, then incorrect names for this package and class will be entered into the generated FoldingUpdater class.  If that happens, the class should be edited to fill in the correct names.

Creating a reference resolver and hyperlinking service

The goal of this step is to create an implementation for a reference resolution service.  This service is not apparent to users, but  it enables a user-visible hyperlinking service (that is implemented by a SAFARI library class), and it contributes to the implementation of a user-visible content assist service (and may be used by clients).  The SAFARI wizard to generate the reference resolver is called "NewReferenceResolver" and is run by invoking

"File" -> "New" -> "IDE Language Support" -> "Editor Services" -> "Reference Resolver"

As with similar wizards, when you run this one, be sure that the name of the project and language are correct, and edit the name of the implementation package and class as needed.  Hit "Finish" when done.

The wizard by default generates a referenceReslovers package with a ReferenceResolver class. The principal methods to be implemented are getLinkTarget(node) and getLinkText(node).  The user can also implement methods that will perform filtering on given link source nodes to eliminate nodes that aren't appropriate in that role.

Example implementations are provided in the generated class that work with the simple expression language used for examples in the other services.  For getLinkTarget(..), the basic logic is to return null for any given node that cannot be a link source, and for a node that can serve as a link sorce, to build a structure of the scopes and declarations of the program (using an AST visitor), then to return the declaration of the given source as found in that structure.  Of course, in general, the details of reference resolution are langauge specific, and the amount of programming required to complete the ReferenceResolver may depend on the functionality provided by avaliable language tools such as a compiler.  If available language tools already support reference resolution, then the ReferenceResolver may just need to retrieve the relevant information from these tools.  On the other hand, if the available language tools do not support reference resolution, then the ReferenceResolver may need to encode the logic necessary to perform resolution.

Once reference resolution is implemented, hyperlinking is automatically enabled in the language editor.  The hyperlinking service is implemented by a SAFARI library class and only depends on the ReferenceResolver to become functional.  Hyperlinking in a SAFARI editor works as it does in an Eclipse JDT Java editor, by control-clicking on a suitable link target.

Note:  The as with the NewFoldingUpdater wizard, the NewHyperlinkDetector wizard needs the name of the package that contains  AST node types.  If the value provided by default is not valid, then the class should be edited to fill in the correct name.

Creating a content-proposal service

The content-proposal service (also known as "content assist") is intended to work in a SAFARI editor similarly to the way as it works in the Ecipse JDT Java editor:  with the cursor position within or at the end of a word (keyword or identifier), typing control-space will bring up a list of proposals for completing the word, possibly including complex expressions or commands, consistent with the prefix of the word up to the point of the cursor and consistent with the syntax and semantics of the program at that point.  Of course, content assist in the JDT Java editor is quite sophisticated.  The functionality of this service in a SAFARI editor depends on how the service is implemented for the language of the editor.

The SAFARI wizard to generate a content-proposal service is called "NewContentProposer" and is run by invoking  

"File" -> "New" -> "IDE Language Support" -> "Editor Services" -> "Content proposer"

Note:  The content proposal service depends on the ReferenceResolver service, so that should be created before this one.

As with previous wizards, when you run this one, be sure that the name of the project and language are correct, edit the name of the implementation package and class as needed, and nit "Finish" when done.

The generated implementation skeleton for this service contains code that works for the simple expression language that is used for examples with other services.  The main method to be implemented by SAFARI users is getContentProposals(..), which takes a parse controller and an offset and returns an array of completion proposals.  The parse controller provides access to the parse tree withiin which proposals are sought, and the offset indicates the point within the source file at which proposals are sought.  The basic logic of the example implementation is simple:   first get the token that covers the offset in the source file and the prefix in that token up to the offset; then get the AST node that corresponds to that token; then, if the node is of an appropriate type, gather and return the completion proposals for the prefix at that node.  To that depth, the logic is largely language-independent.  Gathering the completion proposals will usually be much more language specific.  In the generated example, the focus is on variable; variables that are visible at the offset point are considered, and completion proposals are constructed for those variables with identifiers that match the prefix under consideration.  Implementation for the content proposal service will generally have to address the kind of entities for which proposals are generated (e.g., variables?  key words?), the kind of proposals that are returned (e.g., just identifiers or more complex constructs), and the semantics involved in the gathering of prospective proposals (e.g., visibility rules).

Creating a builder service

The goal of this step is to create a builder for the language.  Builders perform activities like generation or compilation for files in a particular source language.  Builders in Eclipse are associated with projects in a many-to-many relationship.  When a project is built, the various builders for the project are invoked, and each builder performs its respective build action on files in the project that have the appropriate type.  The SAFARI NewBuilder wizard generates a Builder class that must be customized to perform the desired language-specific build activity.

To invoke the NewBuilder wizard, select  

"File" -> "New" -> "IDE Language Support" -> "Core Services" -> "Incremental Builder"

In the wizard:

  • Assure that the names of the project and language are entered correctly.
  • This wizard requires that an id be provided for the builder extension and also allows for the provision of a human-friendly name for the builder extension.   The id is needed because a plugin may have multiple builders and these need to be distinguishable.
  • Although it is not necessary to define a nature to which your builder will be associated, this is a fairly common practice.  Whether a builder is configurable depends on the design of the particular builder.  If the values for "hasNature" and "isConfigurable" are left blank then they are treated as if false.
  • Edit the class field as necessary to specify the desired package and class name for the implementation.
  • The "name" and "value" fields represent a parameter that may be used to pass external information to the builder.  The "run" element of the builders schema in org.eclipse.core.resources allows a builder to have some number of parameter elements (0 or more); in each parameter element, the name and value are required.  As a simplification, the SAFARI NewBuilder wizard provides exactly one parameter element.  Since the name and value field are required, some values must be filled in here, but these values can be ignored by the builder if none are actually needed.  (We expect to make the New Builder wizard more flexible in this regard in the future.)
  • Finally, if your language has a translation into Java, and you want to use a Java debugger on it, check the checkbox "Add SMAP support" if you want to provide source-line mapping between source in your language and its Java representation.
  • Hit "Finish" when done.
The NewBuilder wizard generates one package, two classes, and three plug-in extensions.

The package that is generated is the “safari.builders” package.  It contains the two generated classes, a “Builder” class and a “Nature” class.  The new extensions are a builder extension (extending org.eclipse.core.resources.builders), a nature extension (extending org.eclipse.core.resources.natures), and a problem-marker extension (extending org.eclipse.core.resources.markers).  All of these classes and extensions are created regardless of the values provided to the wizard (in particular, regardless of whether "hasNature" is true or false)..  The problem marker defines a type of marker annotation that the builder can attach to source files to represent problems encountered when building the files.

The Builder class is a skeleton that overrides or implements several methods defined in its abstract parent class, SAFARIBuilderBase:

  •  getErrorMarker(..)
  •  getInfoMarker(..)
  •  getWarningMarker(..)
  •  getPlugin(..)
  •  isSourceFile(..)
  •  isNonRootSourceFile(..)
  •  isOutputFolder(..)
  •  compile(..)

Most of these methods as generated will do reasonable things (although you may tailor any of them if you find a need to do so).

The method isNonRootSourceFile(..) is used to identify files (like C/C++ ".h" files or JikesPG ".gi" files) that should not be compiled directly but that should nevertheless be processed for dependencies.

In any case you will have to edit the compile(..) method, which is really the main “build” method.  This will have to call your “compiler” in an appropriate way for a given source file, retrieve any diagnostic messages that result from the building, and create problem markers for those.  If your build activity runs in an external process, or uses java.io to create output files, you will also need to call IResource.refresh() on any folders that contain generated output files (assuming that they are somewhere in your workspace).

SAFARI defines an interface, IMessageHandler (org.eclipse.uide.editor.IMessageHandler), to facilitate the reporting of error (and other) messages from compilers (or other builders) to interested services.  SAFARI also provides an implementation of this interface, MarkerCreator (org.eclipse.uide.builder), that creates marker annotations for messages received and attaches those to a given source file.

The figure below shows a Builder compile(..) method using a JikesPG-generated ParseController in the role of the compiler and using MarkerCreator to handle parser error messages and create marker annotations on the given source file.

/*
* Implementation of the compile method in a SAFARI generated Builder
* for a subset of JavaScript known as “Jsdiv” using a JikesPG-generated
* Parse Controller and the provided MarkerCreator implementation for
* IMessageHandler
*/

protected void compile(final IFile file, IProgressMonitor monitor) {
try {
// Get a Parse Controller to serve as the “compiler”
IParseController parseController = new JsdivParseController();

// Get a MarkerCreator to handle error messages from the parse controller
// and create corresponding marker annotations on the input file
MarkerCreator markerCreator = new MarkerCreator(file, parseController);

// Initialize the Parse Controller
parseController.initialize(
file.getProjectRelativePath().toString(),
file.getProject(), markerCreator);

// Get file contents for parsing
String contents = ContentExtractionUtility.extractContentsToString(file.getLocation().toString());

// Parse the contents
parseController.parse(contents, false, monitor);

// Refresh the parent
doRefresh(file.getParent());
}
}

catch (Exception e) {
JsdivPlugin.getInstance().writeErrorMsg(e.getMessage());
e.printStackTrace();
}
}
The Nature class extends org.eclipse.uide.core.ProjectNatureBase and provides some simple method implementations and stubs.  These can be used as-is without further modification unless more sophisticated control over building is required for the nature.  (With a new nature this is not likely to be true initially.)

The builder extension will be generated to contain elements representing the various fields from the wizard.  The nature extension will be generated with a “builder” element that refers to the generated builder extension.  (This happens regardless of whether “hasNature” was set to true in the wizard, but the nature need not be applied to any project that contains files in the source language, and the builder for the language can be used independently of the nature).  If the "Add SMAP support" checkbox is checked, then the "addToProject(IProject)" method in the Nature class is augmented to add a language-specific SMAPI nature to the given project.  In the marker extension a marker with id “problem” is defined with super type “org.eclipse.core.resources.problemmarker.”  (Marker types are maintained as in this way as metadata.)

Problems with the MANIFEST.MF file:  On some occasions (that seem to be especially associated with the running of the New Builder wizard),  you may notice an error in your project's MANIFEST.MF file  that is related to the line "Bundle-SymbolicName: leg; singleton:=true".  The error message will say something to the effect that the attribute singleton must be true.  Of course, it has been set to true.  The problem is that different forms of assignment seem to be preferred in different circumstances.  If you encounter this error, try changing "singleton:=true" to "singleton=true" (or vice versa).

A note on Annotations and Markers

When a SAFARI editor runs, the source text is parsed frequently and error messages are reported as annotations that appear in the editor.  These annotations are not associated with the source file and do not appear in the "Problems" view.  When a SAFARI Builder runs, the editor annotations are deleted and a new set of marker annotations are created.  These annotations also appear in the editor (and may seem identical to pre-build editor annotations).   However, the marker annotations are associated with the file and do appear in the "Problems" view.  Building may occur automatically, whenever a file is saved (or a project is cleaned), or building may occur manually, only when explicitly invoked by the user.  Regardless, problem markers will appear when builds occur (provided that there are problems).  When a file is opened in an editor the problem markers are deleted but the file is parsed immediately and new editor annotations are created.

Creating a nature enabler

Once you have created a builder and corresponding nature for your language, you need to associate the nature with a development project (containing source code in your language) in order for the builder to run automatically on the project. Eclipse does not make it easy to manually associate a new nature to an existing project, so SAFARI provides a "Nature Enabler" wizard that creates a context-menu action that will do this for you automatically.

To invoke the Nature Enabler wizard, select
"File" -> "New" -> "IDE Language Support" -> "Core Services" -> "Nature Enabler"

In the wizard, assure that the names of the project and language are correct and hit finish.

Once the wizard has run, when you right-click on a project in your development workspace, you should see a menu item that says "Enable Builder". Clicking on that item will associate the langauge nature with the project and enable the builder to run automatically.

Note that, as currently implemented, the menu actions are made available only for Java projects, so your development project must be a Java project to take advantage of this feature.

Creating a preferences service and pages

SAFARI provides a preference service that is based on the Eclipse Preferences Service (org.eclipse.core.internal.preferences.PreferencesService, since 3.0).  The SAFARI model of preferences is basically that of the Preferences Service.  This is a layered model, with four intrinsicly supported layers.  From most general to most specific, these are:
  1. Default
  2. Configuration (i.e., workspace configuration)
  3. Instance (i.e., workspace instance)
  4. Project
Preferences set at a given level are scoped accordingly, values set at lower levels override values set at higher levels, with default values applying anywhere that a more specific value is lacking.

SAFARI initially supports a a style of preference page with four tabs, one for each of the four preference levels.  The page and the four tabs (with certain annotations and controls) are generated automatically by the SAFARI New Preference Page wizard.  As an alternative, SAFARI also supports the generation of a simple, untabbed preference page.

To invoke the New Preference Page wizard, select
"File" -> "New" -> "IDE Language Support" -> "Core Services" -> "Preference Page"

In the wizard:
  • As usual, assure that the names of the project and language are entered correctly.
  • As a language may have multiple preference pages, you must specify a unique Eclispe identifier and usre-friendly name for a particular page
  • As usual, edit the package and class name for the implementation to be what you want
  • The optional "category" field is used to designate an existing preference page, if any, under which the new page will be listed in the Eclipse preferences menu (Window -> Preferences ...).  To designate the "parent" page, you must provide its Eclipse id.
  • Finally, if you do not wish to generate a full SAFARI preference page, you can generate a simple (untabbed) page with some text by filling that text into the "alternative" field.  (If this field is left empty, then the usual tabbed SAFARI preference page will be generated).
  • Hit "Finish" when done.
The SAFARI preference pages are generated without any preference fields (or without any that are likely to be relevant to your language).  Fields for specific preferences must be added to complete the implementation of a preference page.

Additional information about the SAFARI preferences model and service, and about SAFARI preference pages and fields and their implemenation, can be found in Preferences in SAFARI IDEs.

Using your SAFARI IDE

To use your SAFARI IDE, you will typically create a development workspace that contains one or more development projects in which you will write programs in your language, taking advantage of the services provided by your SAFARI IDE.

To make a development workspace for your SAFARI IDE, you simply need to import the plugin for your SAFARI IDE into a new or existing workspace. (Note: This will have to be a different workspace from the one in which you developed your SAFARI IDE.)

Once you have imported the plugin for your SAFARI IDE into a workspace, any project in the workspace can serve as a development project for your language. As soon as you start creating source files in the language, in any project, your SAFARI IDE will recognize the files and automatically provide IDE services. (Note that recognition of the files is based on the filename extensions that were provided with the initial language description, so you must name the source files with these extensions.)

The one exception to the automatic provision of IDE services relates to natures and builders. Builders for your language will not run automatically on the files within a project unless the corresponding nature has been associated to the project. You can manually add an entry for your builder and nature to the ".project" file for your development project. (See the .project file for org.eclipse.uide for examples.) However, if your project is a Java project, then SAFARI provides a way to do this automatically, through the Nature Enabler wizard.

To be able to take advantage of the Nature Enabler is one reason that you may want to try out your SAFARI IDE using a Java project for development. If it so happens that your language will be translated to Java, then you'll probably want to use a Java project for that reason (or, to look at it the other way, your SAFARI IDE will work well with any existing Java projects that you may be using for development).

Final words of advice

Be careful entering information into the wizards.  The wizards will generally not let you get away without providing information for required fields, but they (and the rest of SAFARI) are not necessarily robust with respect to errors in the provided information.  Errors in the provided information may or may not show up directly and may or may not have confusing effects.

You can update some of the SAFARI information manually.  While the wizards are intended to facilitate the entry of necessary (or at least useful) information at appropriate times and places, you can also manually edit the plugin.xml file to set (or reset) many of the attributes associated with SAFARI extensions.  So, if you want to add an icon or validator to an IDE that is already defined, you can do that without rerunning the wizard.  (This can help to avoid problems that can occur when the wizards are rerun ...)

Be careful when rerunning the wizards!
  1. Several of the wizards generate skeleton files that must be tailored by the user.  If you rerun one of these wizards, the tailored files will be clobbered and updates to them will be lost.  So backup these files where they won't be clobbered.
  2. Several of the wizards add extensions to the plugin.xml file of the IDE project.  Rerunning these wizards does not remove the extensions created by previous invocations.  So, if you want to rerun one of these wizards, you should manually delete the corresponding extension(s) from the plugin.xml file.