Erlang Plugin for NetBeans in Scala#1: Scala Enumeration Implemented a Java Interface

ErlyBird (previous plugin) was written in Java, and based on NetBeans' Generic Languages Framework ( Project Schliemann). As NetBeans 7.0 introducing new  Parsing API, I'm going to migrate Erlang plugin for NetBeans to it, and rewrite in Scala.

Defining a Scala/Java mixed module project in NetBeans' trunk tree is a bit trick, but, since it's plain ant-based project, you can always take over it by writing/modifying build.xml. Challenges are actually from integrating Scala's Class/Object/Trait to an already existed framework. I met this kind of challenge quickly when I tried to implement an enum like class which should implement a Java interface org.netbeans.api.lexer.TokenId?

The signature of TokenId? is:

public interface TokenId {
    String name();
    int ordinal();
    String primaryCategory();
}

From the signature, you can get the hint to implement it as a Java enum. That's true and straightforward in Java. But how about in Scala?

There is an abstract class  Enumeration in Scala's library. The example code shows how to use it by extending it as a singleton Object. But for my case, I have to implement name() and ordinal() methods of TokenId? in the meantime, which brings some inconveniences for get the name() method automatically/magically for each enumeration value.

A quick google search gave some discussions about it, for example, a method reflection and invocation may be a choice to get name() simply implemented. I tried it, but finally dropped, because you cannot guarantee the condition of call stack of name(), it may happen to be called in this Object's own method, which then, may bring infinite cycled calls.

My final code is like:

package org.netbeans.modules.erlang.editor.lexer

object ErlangTokenId extends Enumeration {
  type ErlangTokenId = V

  // Extends Enumeration's inner class Val to get custom enumeration value
  class V(val name:String, val fixedText:String, val primaryCategory:String) extends Val(name) with TokenId {
    override
    def ordinal = id
  }
  object V {
    def apply(name:String, fixedText:String, primaryCategory:String) = new V(name, fixedText, primaryCategory)
  }
  
  val IGNORED = V("IGNORED", null, "ingore")
  val Error = V("Error", null, "error")

  // --- Spaces and comments
  val Ws = V("Ws", null, "whitespace")
  val Nl = V("Nl", null, "whitespace")
  val LineComment = V("LineComment", null, "comment")
  val CommentTag = V("CommentTag", null, "comment")
  val CommentData = V("CommentData", null, "comment")

  // --- Literals
  val IntegerLiteral = V("IntegerLiteral", null, "number")
  val FloatingPointLiteral = V("FloatingPointLiteral", null, "number")
  val CharacterLiteral = V("CharacterLiteral", null, "char")
  val StringLiteral = V("StringLiteral", null, "string")

  // --- Keywords
  val Andalso = V("Andalso", "andalso", "keyword")
  val After = V("After", "after", "keyword")
  val And = V("And", "and", "keyword")
  val Band = V("Band", "band", "keyword")
  val Begin = V("Begin", "begin", "keyword")
  val Bnot = V("Bnot", "bnot", "keyword")
  val Bor = V("Bor", "bor", "keyword")
  val Bsr = V("Bsr", "bsr", "keyword")
  val Bxor = V("Bxor", "bxor", "keyword")
  val Case = V("Case", "case", "keyword")
  val Catch = V("Catch", "catch", "keyword")
  val Cond = V("Cond", "cond", "keyword")
  val Div = V("Div", "div", "keyword")
  val End = V("End", "end", "keyword")
  val Fun = V("Fun", "fun", "keyword")
  val If = V("If", "if", "keyword")
  val Not = V("Not", "not", "keyword")
  val Of = V("Of", "of", "keyword")
  val Orelse = V("Orelse", "orelse", "keyword")
  val Or = V("Or", "or", "keyword")
  val Query = V("Query", "query", "keyword")
  val Receive = V("Receive", "receive", "keyword")
  val Rem = V("Rem", "rem", "keyword")
  val Try = V("Try", "try", "keyword")
  val When = V("When", "when", "keyword")
  val Xor = V("Xor", "xor", "keyword")

  // --- Identifiers
  val Atom = V("Atom", null, "identifier")
  val Var = V("Var", null, "identifier")

  // --- Symbols
  val LParen = V("LParen", "(", "separator")
  val RParen = V("RParan", ")", "separator")
  val LBrace = V("LBrace", "{", "separator")
  val RBrace = V("RBrace", "}", "separator")
  val LBracket = V("LBracket", "[", "separator")
  val RBracket = V("RBracket", "]", "separator")
  val Comma = V("Comma", ",", "separator")
  val Dot = V("Dot", ".", "separator")
  val Semicolon = V("Semicolon", ";", "separator")
  val DBar = V("DBar", "||", "separator")
  val Bar = V("Bar", "|",  "separator")
  val Question = V("Question", "?","separator")
  val DLt = V("DLt", "<<", "separator")
  val LArrow = V("LArrow", "<-", "separator")
  val Lt = V("Lt", "<", "separator")
  val DGt = V("DGt",  >", "separator")
  val Ge = V("Ge",  =", "separator")
  val Gt = V("Gt",  ", "separator")
  val ColonMinus = V("ColonMinus", ":-", "separator")
  val DColon = V("DColon", "::", "separator")
  val Colon = V("Colon", ":", "separator")
  val Hash = V("Hash", "#", "separator")
  val DPlus = V("DPlus", "++", "separator")
  val Plus = V("Plus", "+", "separator")
  val DMinus = V("DMinus", "--", "separator")
  val RArrow = V("RArrow", "->", "separator")
  val Minus = V("Minus", "-", "separator")
  val Star = V("Star", "*", "separator")
  val Ne = V("Ne", "/=", "separator")
  val Slash = V("Slash", "/", "separator")
  val EEq = V("EEq", "=:=", "separator")
  val ENe = V("ENe", "=/=", "separator")
  val DEq = V("DEq", "==", "separator")
  val Le = V("le", "=<", "separator")
  val Eq = V("Eq", "=", "separator")
  val Exclamation = V("Exclamation", "!", "separator")
}

First, I defined a class V which extends Enumeration.Val, and implemented TokenId with an extra field: fixedText.

Then, I have to explicitly put the value's name to this class and pass it to Enumeration.Val's constructor, so function ErlangTokenId.valueOf(String) will work as Java's enum type.

By type ErlangTokenId = V, type ErlangTokenId.V is now aliased as ErlangTokenId, so you can use ErlangTokenId instead of ErlangTokenId.V everywhere now, which exactly gets the effect of one of the behaviors of Java's enum: enum's value is the same type of enum itself.

Comments

1. Yi Ming Woo -- 2009-02-02 08:00

dcaoyuan, any chances of a post on getting started with writing language plugins for NetBeans?

2. Caoyuan -- 2009-02-03 08:00

Yi Ming Woo,

There may be a serial blogs talking about it.

3. utyf -- 2010-01-10 01:02

Do you plan to upgrade Erlang plugin for compability with NetBeans 6.8?

Is this plugin open-source? (I didn't find any fresh sources) If it is, I'd like to improve it.

4. dcaoyuan -- 2010-01-10 04:13

Yes, it's open-source, see these modules "erlang.*"  http://hg.netbeans.org/main/contrib/file/181b35784989

I do have plan to upgrade to NetBeans 6.8, but not sure when I have time.