blob: a7bbd804f7fcc9bdaa7a8d312bb2df1744a90e9b [file] [log] [blame]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<!-- -*- xhtml -*- -->
<title>JavaCC Parser Generator Integration Tutorial for the NetBeans Platform</title>
<link rel="stylesheet" type="text/css" href="https://netbeans.org/netbeans.css"/>
<meta name="AUDIENCE" content="NBUSER"/>
<meta name="TYPE" content="ARTICLE"/>
<meta name="EXPIRES" content="N"/>
<meta name="developer" content="gwielenga@netbeans.org"/>
<meta name="indexed" content="y"/>
<meta name="description"
content="A short guide to integrating a JavaCC Parser into the NetBeans Platform."/>
<!-- Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. -->
<!-- Use is subject to license terms.-->
</head>
<body>
<h1>JavaCC Parser Generator Integration Tutorial for the NetBeans Platform</h1>
<p>This tutorial shows you how to generate
a parser with <a href="http://javacc.java.net/">JavaCC</a>
and use it to create features in a NetBeans editor. </p>
<p class="notes"><b>Note:</b> Prior to starting to work on this tutorial,
you must have completed the
<a href="nbm-javacc-lexer.html">JavaCC Lexer Generator Integration Tutorial</a>,
since that tutorial shows how to create the module structure, file type support,
and lexer used in the instructions that follow.</p>
<p>You will learn how to create several features in a NetBeans editor,
based on your JavaCC parser, such as a syntax error parser, as shown below:</p>
<p><img src="../images/tutorials/javacc/72/result-3.png" alt="source."/></p>
<p><b>Contents</b></p>
<p><img src="../images/articles/74/netbeans_stamp_74_73_72.png" class="stamp" width="114" height="114" alt="Content on this page applies to NetBeans IDE 7.2" title="Content on this page applies to NetBeans IDE 7.2"/></p>
<ul class="toc">
<li><a href="#generating">Generating a Parser from JavaCC</a></li>
<li><a href="#integrating">Integrating the JavaCC Parser with NetBeans APIs</a></li>
<li>Implementing New Features
<ul>
<li><a href="#error-feature">Error Parsing</a></li>
<li><a href="#indent-feature">Indentation</a></li>
<li><a href="#format-feature">Reformatting</a></li>
<li><a href="#brace-feature">Brace Matching</a></li>
<li><a href="#fold-feature">Code Folding</a></li>
</ul>
</li>
</ul>
<p><b>To follow this tutorial, you need the software and resources listed in the following
table.</b></p>
<table>
<tbody>
<tr>
<th class="tblheader" scope="col">Software or Resource</th>
<th class="tblheader" scope="col">Version Required</th>
</tr>
<tr>
<td class="tbltd1"><a href="https://netbeans.org/downloads/index.html">NetBeans IDE</a></td>
<td class="tbltd1">version 7.2 or above</td>
</tr>
<tr>
<td class="tbltd1"><a href="http://java.sun.com/javase/downloads/index.jsp">Java Developer Kit (JDK)</a></td>
<td class="tbltd1">version 7 or above</td>
</tr>
</tbody>
</table>
<p class="tips">For troubleshooting purposes, you are welcome to download the <a href="http://java.net/projects/nb-api-samples/sources/api-samples/show/versions/7.2/tutorials/SimpleJava2">completed tutorial source code</a>.</p>
<!-- ===================================================================================== -->
<h2><a name="generating"></a>Generating a Parser from JavaCC</h2>
<p>Let's now use JavaCC to generate a parser, in the same way as
we generated a lexer in the
<a href="nbm-javacc-lexer.html">JavaCC Lexer Generator Integration Tutorial</a>. We'll need
to edit the JavaCC grammar file less than we did in the previous tutorial, since we're
not going to remove the parser generator as we did last time.</p>
<div class="indent">
<ol>
<li><p>Create a new package named <tt>org.simplejava.jccparser</tt> in your project.
Copy into the new package the same <tt>Java1.5.jj</tt> file that you copied
in the
previous tutorial, from the JavaCC distribution, as before. This time, also
include the "MyToken.java" file:</p>
<br/>
<p><img src="../images/tutorials/javacc/72/parser-2.png" alt="source."/></p>
<br/>
<p>In your project structure, you should now see your new package and new file:</p>
<br/>
<p><img src="../images/tutorials/javacc/72/parser-1.png" alt="source."/></p>
<br/>
<p>We're now going to slightly tweak the <tt>Java1.5.jj</tt> file again so that it fits our
parsing needs.</p>
<br/>
<ul>
<li><p>The <tt>MyToken</tt> class can't be compiled because it is missing a package statement.
Add it:</p>
<pre class="examplecode">package org.simplejava.jccparser;</pre>
<p>The class will still not compile because the implementing class <tt>Token</tt>
does not exist yet. We will generate that class in the next step.</p>
</li>
<li><p>We need to make sure that the classes
that JavaCC will generate for us will be generated with the correct package statements.
Add "package org.simplejava.jccparser;"
to <tt>Java1.5.jj</tt> file after the "PARSER_BEGIN(JavaParser)" line:</p>
<pre class="examplecode">PARSER_BEGIN(JavaParser)
<b>package org.simplejava.jccparser;</b>
import java.io.*;</pre>
</li>
</ul></li>
<li><p>That's all we need to do. The <tt>Java1.5.jj</tt> file is ready now and we
can generate our parser from the command line, in the same
way as in the previous tutorial. The result should be as follows:</p>
<br/>
<p><img src="../images/tutorials/javacc/72/parser-3.png" alt="source."/></p>
<br/>
<p>As you can see, JavaCC has generated several files, which we will use in the next sections.
All the files should be compilable, that is, there should be no error
marks anywhere in the module, as can be seen in the screenshot above.</p>
</li>
</ol>
</div>
<p>You've now completed the JavaCC part of the tutorial.
The time has come to use the generated files to extend
your NetBeans Lexer plugin.</p>
<!-- ======================================================================================= -->
<h2><a name="integrating"></a>Integrating the JavaCC Parser with NetBeans APIs</h2>
<p>In this section, we take the files generated in the previous section
and integrate them with the <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/overview-summary.html">NetBeans Parsing API</a>.</p>
<div class="indent">
<ol>
<li><p>In the Projects window, right-click the Libraries node, and choose
Add Module Dependency. Look for the "Parsing API" module in the list.
When you click OK, you should see the "Parsing API" module is now
a dependency in your module:</p>
<br/>
<p><img src="../images/tutorials/javacc/72/parser-4.png" alt="source."/></p>
<br/>
</li>
<li><p>In your module, create a new package named <tt>org.simplejava.parser</tt>.</p>
</li>
<li><p>The first NetBeans APIclass you need to implement is <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/spi/Parser.html">org.netbeans.modules.parsing.spi.Parser</a></tt>.
Create a class named <tt>SJParser</tt> and define it as follows:</p>
<pre class="examplecode">package org.simplejava.parser;
import java.io.Reader;
import java.io.StringReader;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.event.ChangeListener;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/api/Snapshot.html">org.netbeans.modules.parsing.api.Snapshot</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/api/Task.html">org.netbeans.modules.parsing.api.Task</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/spi/Parser.html">org.netbeans.modules.parsing.spi.Parser</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/spi/ParserResultTask.html">org.netbeans.modules.parsing.spi.Parser.Result</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/spi/SourceModificationEvent.html">org.netbeans.modules.parsing.spi.SourceModificationEvent</a>;
import org.simplejava.jccparser.JavaParser;
public class SJParser extends <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/spi/Parser.html">Parser</a> {
private Snapshot snapshot;
private JavaParser javaParser;
@Override
public void parse (Snapshot snapshot, Task task, SourceModificationEvent event) {
this.snapshot = snapshot;
Reader reader = new StringReader(snapshot.getText().toString ());
javaParser = new JavaParser(reader);
try {
javaParser.CompilationUnit ();
} catch (org.simplejava.jccparser.ParseException ex) {
Logger.getLogger (SJParser.class.getName()).log (Level.WARNING, null, ex);
}
}
@Override
public Result getResult (Task task) {
return new SJParserResult (snapshot, javaParser);
}
@Override
public void cancel () {
}
@Override
public void addChangeListener (ChangeListener changeListener) {
}
@Override
public void removeChangeListener (ChangeListener changeListener) {
}
public static class SJParserResult extends Result {
private JavaParser javaParser;
private boolean valid = true;
SJParserResult (Snapshot snapshot, JavaParser javaParser) {
super (snapshot);
this.javaParser = javaParser;
}
public JavaParser getJavaParser () throws org.netbeans.modules.parsing.spi.ParseException {
if (!valid) throw new org.netbeans.modules.parsing.spi.ParseException ();
return javaParser;
}
@Override
protected void invalidate () {
valid = false;
}
}
}</pre></li>
<li><p>Register the parser in the language class created
in the previous tutorial, as follows:</p>
<pre class="java">package org.simplejava;
import org.netbeans.api.lexer.Language;
import org.netbeans.modules.csl.spi.DefaultLanguageConfig;
import org.netbeans.modules.csl.spi.LanguageRegistration;
import org.netbeans.modules.parsing.spi.Parser;
import org.simplejava.lexer.SJTokenId;
import org.simplejava.parser.SJParser;
@LanguageRegistration(mimeType = "text/x-sj")
public class SJLanguage extends DefaultLanguageConfig {
@Override
public Language getLexerLanguage() {
return SJTokenId.getLanguage();
}
@Override
public String getDisplayName() {
return "SJ";
}
<b>@Override
public Parser getParser() {
return new SJParser();
}</b>
}</pre>
</li>
</ol>
</div>
<p>You now have an implementation of the NetBeans Parsing API based
on a JavaCC parser generated from a JavaCC grammar definition.
Your parser generated by JavaCC is registered
in the NetBeans Platform. You can compile and run the module.
However, your parser will never be called simply because
you don't have code asking for the parser results. Since
there is no client of your parser yet, let's create one in
the next section.</p>
<!-- ======================================================================================= -->
<h2><a name="error-feature"></a>Implementing a New Feature: Error Parsing</h2>
<p>Now you will create a first client of your <tt>SJParser</tt>.
This client (task) will show syntax errors in the
NetBeans editor sidebar, also known as its "gutter".</p>
<p>Before working on the related code, we need to
make some modifications to the generated
parser. The parser throws a <tt>ParseException</tt> when it finds
the first error in the source code. This is the default behavior of parsers
generated by JavaCC. But in the NetBeans editor we need to detect more than
just one syntax error. Therefore, we need to add some simple error recovery
to the parser before integrating the NetBeans error parsing code with it.</p>
<div class="indent">
<h3><a name="error-feature-1"></a>Adding Simple Error Recovery to the Parser</h3>
<div class="indent">
<ol>
<li><p>The tweaks below should both be done in <tt>Java1.5.jj</tt> file in your
<tt>org.simplejava.jccparser</tt> package.</p>
<ul>
<li><p>Change "ERROR_REPORTING = false;" to "ERROR_REPORTING = true;":</p>
<pre class="examplecode">options {
JAVA_UNICODE_ESCAPE = true;
<b>ERROR_REPORTING = true;</b>
STATIC = false;
COMMON_TOKEN_ACTION = false;
TOKEN_FACTORY = "MyToken";
JDK_VERSION = "1.5";
}</pre>
</li>
<li><p>Add "import java.util.*;" to your Java1.5.jj file:</p>
<pre class="examplecode">PARSER_BEGIN(JavaParser)
package org.simplejava.jccparser;
import java.io.*;
<b>import java.util.*;</b></pre>
</li></ul></li>
<li><p>Run JavaCC on the <tt>Java1.5.jj</tt> file again, the same way as
you did in the previous section.</p>
</li>
<li><p>These additions and changes
should be done in your <tt>JavaParser</tt> class.</p>
<br/>
<ul>
<li><p>Add the following method to your <tt>JavaParser</tt> body:</p>
<pre class="examplecode">public List&lt;ParseException&gt; syntaxErrors = new ArrayList&lt;ParseException&gt;();
void recover (ParseException ex, int recoveryPoint) {
syntaxErrors.add (ex);
Token t;
do {
t = getNextToken ();
} while (t.kind != EOF && t.kind != recoveryPoint);
}</pre>
</li>
<li>Catch <tt>ParseExceptions</tt> in <tt>CompilationUnit</tt>,
<tt>FieldDeclaration</tt>, <tt>MethodDeclaration</tt>,
and <tt>Statement</tt>:
<pre class="examplecode">final public void CompilationUnit() throws ParseException {
<b>try {</b>
if (jj_2_1(2147483647)) {
PackageDeclaration();
} else {
;
}
label_1:
while (true) {
switch ((jj_ntk == -1) ? jj_ntk() : jj_ntk) {
case IMPORT:
;
break;
default:
break label_1;
}
ImportDeclaration();
}
label_2:
while (true) {
switch ((jj_ntk == -1) ? jj_ntk() : jj_ntk) {
case ABSTRACT:
case CLASS:
case ENUM:
case FINAL:
case INTERFACE:
case NATIVE:
case PRIVATE:
case PROTECTED:
case PUBLIC:
case STATIC:
case STRICTFP:
case SYNCHRONIZED:
case TRANSIENT:
case VOLATILE:
case SEMICOLON:
case AT:
;
break;
default:
break label_2;
}
TypeDeclaration();
}
switch ((jj_ntk == -1) ? jj_ntk() : jj_ntk) {
case 127:
jj_consume_token(127);
break;
default:
;
}
switch ((jj_ntk == -1) ? jj_ntk() : jj_ntk) {
case STUFF_TO_IGNORE:
jj_consume_token(STUFF_TO_IGNORE);
break;
default:
;
}
jj_consume_token(0);
<b>} catch (ParseException ex) {
recover(ex, SEMICOLON);
}</b>
}</pre>
<pre class="examplecode">final public void FieldDeclaration(int modifiers) throws ParseException {
<b>try {</b>
Type();
VariableDeclarator();
label_11:
while (true) {
switch ((jj_ntk == -1) ? jj_ntk() : jj_ntk) {
case COMMA:
;
break;
default:
break label_11;
}
jj_consume_token(COMMA);
VariableDeclarator();
}
jj_consume_token(SEMICOLON);
<b>} catch (ParseException ex) {
recover(ex, SEMICOLON);
}</b>
}</pre>
<pre class="examplecode">final public void MethodDeclaration(int modifiers) throws ParseException {
<b>try {</b>
switch ((jj_ntk == -1) ? jj_ntk() : jj_ntk) {
case LT:
TypeParameters();
break;
default:
;
}
ResultType();
MethodDeclarator();
switch ((jj_ntk == -1) ? jj_ntk() : jj_ntk) {
case THROWS:
jj_consume_token(THROWS);
NameList();
break;
default:
;
}
switch ((jj_ntk == -1) ? jj_ntk() : jj_ntk) {
case LBRACE:
Block();
break;
case SEMICOLON:
jj_consume_token(SEMICOLON);
break;
default:
jj_consume_token(-1);
throw new ParseException();
}
<b>} catch (ParseException ex) {
recover(ex, SEMICOLON);
}</b>
}</pre>
<pre class="examplecode">final public void Statement() throws ParseException {
<b>try {</b>
if (jj_2_36(2)) {
LabeledStatement();
} else {
switch ((jj_ntk == -1) ? jj_ntk() : jj_ntk) {
case ASSERT:
AssertStatement();
break;
case LBRACE:
Block();
break;
case SEMICOLON:
EmptyStatement();
break;
case BOOLEAN:
case BYTE:
case CHAR:
case DOUBLE:
case FALSE:
case FLOAT:
case INT:
case LONG:
case NEW:
case NULL:
case SHORT:
case SUPER:
case THIS:
case TRUE:
case VOID:
case INTEGER_LITERAL:
case FLOATING_POINT_LITERAL:
case CHARACTER_LITERAL:
case STRING_LITERAL:
case IDENTIFIER:
case LPAREN:
case INCR:
case DECR:
StatementExpression();
jj_consume_token(SEMICOLON);
break;
case SWITCH:
SwitchStatement();
break;
case IF:
IfStatement();
break;
case WHILE:
WhileStatement();
break;
case DO:
DoStatement();
break;
case FOR:
ForStatement();
break;
case BREAK:
BreakStatement();
break;
case CONTINUE:
ContinueStatement();
break;
case RETURN:
ReturnStatement();
break;
case THROW:
ThrowStatement();
break;
case SYNCHRONIZED:
SynchronizedStatement();
break;
case TRY:
TryStatement();
break;
default:
jj_consume_token(-1);
throw new ParseException();
}
}
<b>} catch (ParseException ex) {
recover(ex, SEMICOLON);
}</b>
}</pre></li></ul></li>
</ol>
</div>
<p>We have added some very basic error recovery to our
parser so that we can display some
syntax errors in the NetBeans editor in the next section.</p>
<h3><a name="error-feature-2"></a>Integrating Syntax Error Reporting</h3>
<p>At this point, we're ready to implement our first <tt>ParserResultTask</tt>.
This task consists of three standard steps:</p>
<div class="indent">
<ol><li>Create a factory, i.e., <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/spi/TaskFactory.html">TaskFactory</a></tt>.</li>
<li>Create a task, i.e., <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/spi/ParserResultTask.html">ParserResultTask</a></tt>.</li>
<li>Register the factory in the layer file.</li>
</ol>
</div>
<p>The above steps are standard in the sense that they are
common to all tasks implementing the NetBeans Parsing API.</p>
<div class="indent">
<ol>
<li><p>Add dependencies on the NetBeans "Editor Hints" module and the
"MIME Lookup API" module.</p></li>
<li><p>Create the <tt>SJSyntaxErrorHighlightingTask</tt> class:</p>
<pre class="examplecode">package org.simplejava.parser;
import java.util.ArrayList;
import java.util.List;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.StyledDocument;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/spi/Parser.Result.html">org.netbeans.modules.parsing.spi.Parser.Result</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/spi/ParserResultTask.html">org.netbeans.modules.parsing.spi.ParserResultTask</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/spi/Scheduler.html">org.netbeans.modules.parsing.spi.Scheduler</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/spi/SchedulerEvent.html">org.netbeans.modules.parsing.spi.SchedulerEvent</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-spi-editor-hints/org/netbeans/spi/editor/hints/ErrorDescription.html">org.netbeans.spi.editor.hints.ErrorDescription</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-spi-editor-hints/org/netbeans/spi/editor/hints/ErrorDescriptionFactory.html">org.netbeans.spi.editor.hints.ErrorDescriptionFactory</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-spi-editor-hints/org/netbeans/spi/editor/hints/HintsController.html">org.netbeans.spi.editor.hints.HintsController</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-spi-editor-hints/org/netbeans/spi/editor/hints/Severity.html">org.netbeans.spi.editor.hints.Severity</a>;
import org.openide.text.NbDocument;
import org.openide.util.Exceptions;
import org.simplejava.jccparser.ParseException;
import org.simplejava.jccparser.Token;
import org.simplejava.parser.SJParser.SJParserResult;
public class SJSyntaxErrorHighlightingTask extends <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/spi/ParserResultTask.html">ParserResultTask</a> {
@Override
public void run (Result result, SchedulerEvent event) {
try {
SJParserResult sjResult = (SJParserResult) result;
List&lt;ParseException&gt; syntaxErrors = sjResult.getJavaParser ().syntaxErrors;
Document document = result.getSnapshot ().getSource ().getDocument (false);
List&lt;ErrorDescription&gt; errors = new ArrayList&lt;ErrorDescription&gt; ();
for (ParseException syntaxError : syntaxErrors) {
Token token = syntaxError.currentToken;
int start = NbDocument.findLineOffset ((StyledDocument) document, token.beginLine - 1) + token.beginColumn - 1;
int end = NbDocument.findLineOffset ((StyledDocument) document, token.endLine - 1) + token.endColumn;
ErrorDescription errorDescription = ErrorDescriptionFactory.createErrorDescription(
Severity.ERROR,
syntaxError.getMessage (),
document,
document.createPosition(start),
document.createPosition(end)
);
errors.add (errorDescription);
}
HintsController.setErrors (document, "simple-java", errors);
} catch (BadLocationException ex1) {
Exceptions.printStackTrace (ex1);
} catch (org.netbeans.modules.parsing.spi.ParseException ex1) {
Exceptions.printStackTrace (ex1);
}
}
@Override
public int getPriority () {
return 100;
}
@Override
public Class getSchedulerClass () {
return Scheduler.EDITOR_SENSITIVE_TASK_SCHEDULER;
}
@Override
public void cancel () {
}
}</pre></li>
<li><p>Create the <tt>SJSyntaxErrorHighlightingTaskFactory</tt> class
in the <tt>org.simplejava.parser</tt> package:</p>
<pre class="examplecode">package org.simplejava.parser;
import java.util.Collection;
import java.util.Collections;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/api/Snapshot.html">org.netbeans.modules.parsing.api.Snapshot</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/spi/TaskFactory.html">org.netbeans.modules.parsing.spi.TaskFactory</a>;
@MimeRegistration(mimeType="text/x-sj",service=TaskFactory.class)
public class SJSyntaxErrorHighlightingTaskFactory extends <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-parsing-api/org/netbeans/modules/parsing/spi/TaskFactory.html">TaskFactory</a> {
@Override
public Collection create (Snapshot snapshot) {
return Collections.singleton (new SJSyntaxErrorHighlightingTask());
}
}</pre></li>
</ol>
</div>
<p>When you install the module into your application and make a syntax error in a SJ file,
you should see the error highlighting in the sidebar of the
NetBeans editor:</p>
<br/>
<p><img src="../images/tutorials/javacc/72/result-3.png" alt="source."/></p>
<br/>
</div>
<!-- ======================================================================================= -->
<h2><a name="indent-feature"></a>Implementing a New Feature: Indentation</h2>
<p>Next, we'll create the skeleton of
an indentation task for our language.</p>
<div class="indent">
<ol>
<li>Add a dependency on the "<a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/overview-summary.html">Editor Indentation</a>" module.</li>
<li><p>Create a new <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/IndentTask.html">IndentTask</a></tt>:</p>
<pre class="examplecode">package org.simplejava.parser;
import javax.swing.text.BadLocationException;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/Context.html">org.netbeans.modules.editor.indent.spi.Context</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/ExtraLock.html">org.netbeans.modules.editor.indent.spi.ExtraLock</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/IndentTask.html">org.netbeans.modules.editor.indent.spi.IndentTask</a>;
import org.openide.awt.StatusDisplayer;
public class SJIndentTask implements <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/IndentTask.html">IndentTask</a> {
private Context context;
SJIndentTask(Context context) {
this.context = context;
}
@Override
public void reindent() throws BadLocationException {
StatusDisplayer.getDefault().setStatusText("We will indent this now...");
}
@Override
public ExtraLock indentLock() {
return null;
}
}</pre>
<p class="notes"><b>Note:</b> The indent task will
make a callback to the <tt>reindent()</tt> method
when the Enter key is pressed in the NetBeans editor.
The <tt>Context</tt> object contains everything that
you need, including the editor document object.
To complete the above implementation,
it should be a matter of taking the text after the cursor
and before the next line to indent the code as desired.</p>
</li>
<li><p>Create a new <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/IndentTask.Factory.html">IndentTask.Factory</a></tt>:</p>
<pre class="java">package org.simplejava.parser;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/Context.html">org.netbeans.modules.editor.indent.spi.Context</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/IndentTask.html">org.netbeans.modules.editor.indent.spi.IndentTask</a>;
@MimeRegistration(mimeType="text/x-sj",service=IndentTask.Factory.class)
public class SJIndentTaskFactory implements <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/IndentTask.Factory.html">IndentTask.Factory</a> {
@Override
public IndentTask createTask(Context context) {
return new SJIndentTask(context);
}
}</pre>
</li>
</ol>
</div>
<p>When you install the module into the application, open an SJ file,
and press Enter, you will see a message
in the status bar, showing you that the indentation integration is working
correctly.</p>
<!-- ======================================================================================= -->
<h2><a name="format-feature"></a>Implementing a New Feature: Reformatting</h2>
<p>Next, we'll create the skeleton of
a reformat task for our language.</p>
<div class="indent">
<ol>
<li>If you have not already done so in the previous section,
add a dependency on the "<a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/overview-summary.html">Editor Indentation</a>" module.</li>
<li><p>Create a new <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/ReformatTask.html">ReformatTask</a></tt>:</p>
<pre class="examplecode">package org.simplejava.parser;
import javax.swing.text.BadLocationException;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/Context.html">org.netbeans.modules.editor.indent.spi.Context</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/ExtraLock.html">org.netbeans.modules.editor.indent.spi.ExtraLock</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/ReformatTask.html">org.netbeans.modules.editor.indent.spi.ReformatTask</a>;
import org.openide.awt.StatusDisplayer;
public class SJReformatTask implements <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/ReformatTask.html">ReformatTask</a> {
private Context context;
public SJReformatTask(Context context) {
this.context = context;
}
@Override
public void reformat() throws BadLocationException {
StatusDisplayer.getDefault().setStatusText("We will format this now...");
}
@Override
public ExtraLock reformatLock() {
return null;
}
}</pre>
<p class="notes"><b>Note:</b> The reformat task will
make a callback to the <tt>reformat()</tt> method
when Alt-Shift-F is pressed in the NetBeans editor.
The <tt>Context</tt> object contains everything that
you need, including the editor document object.
To complete the above reformatting,
it should be a matter of taking the text after the cursor
and before the next line to reformat the code as desired.</p>
</li>
<li><p>Create a new <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/ReformatTask.Factory.html">ReformatTask.Factory</a></tt>:</p>
<pre class="java">package org.simplejava.parser;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/Context.html">org.netbeans.modules.editor.indent.spi.Context</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/ReformatTask.Factory.html">org.netbeans.modules.editor.indent.spi.ReformatTask</a>;
@MimeRegistration(mimeType="text/x-sj",service=ReformatTask.Factory.class)
public class SJReformatTaskFactory implements <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-indent/org/netbeans/modules/editor/indent/spi/ReformatTask.Factory.html">ReformatTask.Factory</a> {
@Override
public ReformatTask createTask(Context context) {
return new SJReformatTask(context);
}
}</pre>
</li>
</div>
</ol>
<p>When you install the module into the application, open an SJ file,
and choose Source | Format (Alt-Shift-F), you will see a message
in the status bar, showing you that the extension point is working
correctly.</p>
<!-- ======================================================================================= -->
<h2><a name="brace-feature"></a>Implementing a New Feature: Brace Matching</h2>
<p>Now, let's look at brace matching. When the user selects an opening brace,
the closing brace should be highlighted, and vice versa. Moreover, when Ctrl-[ is pressed
on the keyboard, the cursor should move back and forth between matching braces.</p>
<p class="tips">This feature is especially useful
if your language is likely to be used to create deeply nested code structures.
</p>
<p>In the first screenshot, the opening brace is selected, which results
in it being highlighted, together with the closing brace, so that you
can see where a code phrase or code block begins and ends and you can
toggle between them by pressing Ctrl-[:</p>
<p><img style="border:1px solid black" src="../images/tutorials/javacc/72/add-brace-1.png" alt="source."/></p>
<p>Similarly, here another code block is made visible by selecting
either the opening or closing brace, causing the matching
brace to also be highlighted, and enabling the cursor to be
toggled between the matching braces via Ctrl-[:</p>
<p><img style="border:1px solid black" src="../images/tutorials/javacc/72/add-brace-2.png" alt="source."/></p>
<div class="indent">
<ol>
<li>Add a dependency on the "<a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-bracesmatching/overview-summary.html">Editor Brace Matching</a>" module.</li>
<li><p>Create a new <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-bracesmatching/org/netbeans/spi/editor/bracesmatching/BracesMatcherFactory.html">BracesMatcherFactory</a></tt>:</p>
<pre class="java">package org.simplejava.parser;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-bracesmatching/org/netbeans/spi/editor/bracesmatching/BracesMatcher.html">org.netbeans.spi.editor.bracesmatching.BracesMatcher</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-bracesmatching/org/netbeans/spi/editor/bracesmatching/BracesMatcherFactory.html">org.netbeans.spi.editor.bracesmatching.BracesMatcherFactory</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-bracesmatching/org/netbeans/spi/editor/bracesmatching/MatcherContext.html">org.netbeans.spi.editor.bracesmatching.MatcherContext</a>;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-bracesmatching/org/netbeans/spi/editor/bracesmatching/support/BracesMatcherSupport.html">org.netbeans.spi.editor.bracesmatching.support.BracesMatcherSupport</a>;
@MimeRegistration(mimeType="text/x-sj",service=BracesMatcherFactory.class)
public class SJBracesMatcherFactory implements <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-bracesmatching/org/netbeans/spi/editor/bracesmatching/BracesMatcherFactory.html">BracesMatcherFactory</a> {
@Override
public BracesMatcher createMatcher(MatcherContext context) {
return BracesMatcherSupport.defaultMatcher(context, -1, -1);
}
}</pre>
<p class="tips">The <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-bracesmatching/org/netbeans/spi/editor/bracesmatching/support/BracesMatcherSupport.html">BracesMatcherSupport</a></tt> package provides
a number of useful implementations of <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-bracesmatching/org/netbeans/spi/editor/bracesmatching/BracesMatcher.html">BracesMatcher</a></tt>. One of these is used
in the code above.</p>
</li>
</div>
</ol>
<p>When you install the module into the application, open an SJ file,
and select a brace, you should see that the brace is highlighted,
together with its matching brace. Press Ctrl-[ to toggle between
matching braces.</p>
<!-- ======================================================================================= -->
<h2><a name="fold-feature"></a>Implementing a New Feature: Code Folding</h2>
<p>The "<a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-fold/overview-summary.html">Editor Code Folding</a>" module
provides the functionality you need to implement for creating your own code folds.</p>
<p>In this tutorial, we will create a code fold for the "FORMAL_COMMENT" token provided
by our lexer:</p>
<p><img style="border:1px solid black" src="../images/tutorials/javacc/72/add-fold-1.png" alt="source."/></p>
<p>When collapsed, the fold will look like this:</p>
<p><img style="border:1px solid black" src="../images/tutorials/javacc/72/add-fold-2.png" alt="source."/></p>
<div class="indent">
<ol>
<li>Add a dependency on the "<a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-fold/overview-summary.html">Editor Code Folding</a>" module.</li>
<li><p>Create a new <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-fold/org/netbeans/spi/editor/fold/FoldManager.html">FoldManager</a></tt>:</p>
<pre class="examplecode">package org.simplejava.parser;
import javax.swing.event.DocumentEvent;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.netbeans.api.editor.fold.Fold;
import org.netbeans.api.editor.fold.FoldHierarchy;
import org.netbeans.api.editor.fold.FoldType;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-fold/org/netbeans/spi/editor/fold/FoldHierarchyTransaction.html">org.netbeans.spi.editor.fold.FoldHierarchyTransaction</a>;
import org.netbeans.spi.editor.fold.FoldManager;
import <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-fold/org/netbeans/spi/editor/fold/FoldOperation.html">org.netbeans.spi.editor.fold.FoldOperation</a>;
import org.openide.util.Exceptions;
import org.simplejava.lexer.SJTokenId;
public class SJFoldManager implements <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-fold/org/netbeans/spi/editor/fold/FoldManager.html">FoldManager</a> {
private FoldOperation operation;
public static final FoldType COMMENT_FOLD_TYPE = new FoldType("/*...*/");
@Override
public void init(FoldOperation operation) {
this.operation = operation;
}
@Override
public void initFolds(FoldHierarchyTransaction transaction) {
FoldHierarchy hierarchy = operation.getHierarchy();
Document document = hierarchy.getComponent().getDocument();
TokenHierarchy&lt;Document&gt; hi = TokenHierarchy.get(document);
TokenSequence&lt;SJTokenId&gt; ts = (TokenSequence&lt;SJTokenId&gt;) hi.tokenSequence();
FoldType type = null;
int start = 0;
int offset = 0;
while (ts.moveNext()) {
offset = ts.offset();
Token&lt;SJTokenId&gt; token = ts.token();
SJTokenId id = token.id();
if (id.name().equals("FORMAL_COMMENT") && type == null) {
type = COMMENT_FOLD_TYPE;
start = offset;
try {
operation.addToHierarchy(
type,
type.toString(),
false,
start,
offset + token.length(),
0,
0,
hierarchy,
transaction);
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
}
}
@Override
public void insertUpdate(DocumentEvent de, FoldHierarchyTransaction fht) {
}
@Override
public void removeUpdate(DocumentEvent de, FoldHierarchyTransaction fht) {
}
@Override
public void changedUpdate(DocumentEvent de, FoldHierarchyTransaction fht) {
}
@Override
public void removeEmptyNotify(Fold fold) {
}
@Override
public void removeDamagedNotify(Fold fold) {
}
@Override
public void expandNotify(Fold fold) {
}
@Override
public void release() {
}
}</pre>
</li>
<li><p>Create a new <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-fold/org/netbeans/spi/editor/fold/FoldManagerFactory.html">FoldManagerFactory</a></tt>:</p>
<pre class="java">package org.simplejava.parser;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
<a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-fold/org/netbeans/spi/editor/fold/FoldManager.html">import org.netbeans.spi.editor.fold.FoldManager</a>;
<a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-fold/org/netbeans/spi/editor/fold/FoldManagerFactory.html">import org.netbeans.spi.editor.fold.FoldManagerFactory</a>;
@MimeRegistration(mimeType="text/x-sj",service=FoldManagerFactory.class)
public class SJFoldManagerFactory implements <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-editor-fold/org/netbeans/spi/editor/fold/FoldManagerFactory.html">FoldManagerFactory</a> {
@Override
public FoldManager createFoldManager() {
return new SJFoldManager();
}
}</pre>
</li>
</ol>
</div>
<p>When you install the module into the application, open an SJ file,
and type a multiline comment at the top of the file, as shown
at the start of this section, a code fold will automatically
appear around the comment.</p>
<!-- ======================================================================================= -->
<p></p>
<div class="feedback-box"><a href="https://netbeans.org/about/contact_form.html?to=3&amp;subject=Feedback:%20JavaCC%20Parser%207.2%20Tutorial">Send Us Your Feedback</a></div>
<!-- ======================================================================================== -->
<h2><a name="nextsteps"></a>Next Steps</h2>
<p class="tips">This tutorial is the official version of
the second part of <a href="http://wiki.netbeans.org/How_to_create_support_for_a_new_language">http://wiki.netbeans.org/How_to_create_support_for_a_new_language</a>,
which, aside from being a rough draft, is partly out of date for the NetBeans Platform.</p>
<p>For more information about creating and developing NetBeans modules, see the following resources: </p>
<ul>
<li><a href="https://platform.netbeans.org/index.html">NetBeans Platform Homepage</a></li>
<li><a href="https://netbeans.org/download/dev/javadoc/">NetBeans API List (Current Development Version)</a></li>
<li><a href="https://netbeans.org/kb/trails/platform.html">Other Related Tutorials</a></li>
</ul>
<!-- ======================================================================================== -->
<!--<h2><a name="version"></a>Versioning </h2>
<table width="76%" >
<tbody>
<tr>
<td>
<b>Version</b>
</td>
<td>
<b>Date</b>
</td>
<td>
<b>Changes</b>
</td>
</tr>
<tr>
<td>
1
</td>
<td>
9 January 2012
</td>
<td>
<ul><li>Initial version.</li>
<li>To do:
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</li>
</ul>
</td>
</tr>
</tbody>
</table>-->
</body>
</html>