Pull to refresh

Создаем плагин для Netbeans на примере языка Vala

Reading time 10 min
Views 3.9K
Vala — это язык программирования, который создавался как замена языка C при разработке приложений для Gnome. При этом язык на мой взгляд удался, в своем синтаксисе взял многое из C# и Java. Прочитать подробнее о нем можно на Хабре или сайте Gnome. Захотев попробовать этот проект в деле столкнулся с тем, что по текущий момент для него не существует качественного IDE или плагина для IDE, которая позволила бы комфортно на нем разрабатывать.

В результате был создан плагин для NetBeans IDE, которая на мой взгляд является одной из лучших open source IDE (наряду с Eclipse). Тем не менее информация о том, как именно разрабатывать модули и плагины для NetBeans Plaform довольно разрознена, а на русском языке практически ничего нет.



Парсер для языка


Первое, что необходимо сделать для поддержки нового языка в NetBeans, это написать лексический и синтаксический анализатор этого языка. Вы можете написать их самостоятельно, или использовать какие то инструменты, которые помогут вам это сделать. Инструментами могут быть например:


Для реализации плагина я выбрал ANTLR, как один из наиболее популярных, а именно — ANTLRv3. Он умеет генерировать код на Java, что позволяет его относительно легко интегрировать в плагин для NetBeans Platform.
Для ANTLR необходимо написать описание языка с использованием LL грамматики. Язык этот близок к РБНФ и во многом интуитивно понятен.

Если упростить, то описание грамматика ANTLR состоит из правил двух типов — lexer и grammar. Правилами lexer описываются токены, которые анализируются на стадии лексического разбора. Имена правил lexer пишутся в верхнем регистре, а правил grammar в нижнем.

Пример правила lexer:
ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')* ;
Это правило выделяет в исходном коде идентификаторы, а именно — слова, которые начинаются с латинской буквы или подчеркивания и не содержат соответственно цифры, латинские символы или подчеркивания. Все соответствующие правилам lexer последовательности символов будут выделены в токены.

После разбора токенов выполняется синтаксический анализ, который генерирует абстрактное синтаксическое дерево или AST. Для генерации AST используются правила grammar.

Пример правила grammar:
try_statement : TRY block (catch_clause)* (finally_clause)?;
Этим правилом описывается блок try… catch. Правило состоит из последовательности токенов и/или других имен правил grammar. Символ '*' в данном случае означает, что блок в скобках catch_clause быть повторен несколько раз или отсутствовать. Символ '?' — блок finally_clause может присутствовать только один раз или его может не быть.

Пожалуй на этом закончим с языком ANTLR, на сайте есть документация и множество примеров. Там же вы можете найти ANTLRWorks, который поможет при отладке правил. Он отображает разобранное дерево в графическом и довольно наглядном виде.
Посмотреть ANTLR описание синтаксиса языка Vala, который написан был для плагина можно на github

Модуль для NetBeans


1) Создаем проект


Для начала необходимо создать проект модуля NetBeans, через меню New Project | NetBeans Modules | Module. Поставьте галку «Standalone Module». Необходимо указать корректное название проекта, название модуля и отметить галку «Generate XML Layer». Последнее действие создаст файл layer.xml, который будет содержать имена классов реализующих поведение плагина и другие параметры вашего модуля.

2) Добавляем поддержку .vala и .vapi файлов в модуль


NetBeans работает с файлами проекта через Datasystems API и Filesystems API. Добавить поддержку новых расширений можно выбрав корневой элемент проекта, далее меню New | Other | Module Development | File Type.

Эти действия приведут к созданию простого класса для работы с DataObject, в нашем случае это ValaDataObject, а также этот класс будет зарегистрирован в layer.xml как обработчик файлов .vala:

<filesystem>
  <folder name="Loaders">
    <folder name="text">
      <folder name="x-vala">
        <folder name="Actions">
...
        </folder>
      <folder name="Factories">
        <file name="ValaDataLoader.instance">
          <attr name="SystemFileSystem.icon" urlvalue="nbresloc:/org/carbonfx/valaproject/vala.png"/>
          <attr name="dataObjectClass" stringvalue="org.carbonfx.valaproject.ValaDataObject"/>
          <attr name="instanceCreate" methodvalue="org.openide.loaders.DataLoaderPool.factory"/>
          <attr name="mimeType" stringvalue="text/x-vala"/>
        </file>
      </folder>
    </folder>
...


* This source code was highlighted with Source Code Highlighter.


Подсветка синтаксиса


Теперь у нас есть все, чтобы добавить в модуль поддержку подсветки синтаксиса нового языка.

1) Подключаем файлы парсера сгенерированные ANTLR к модулю


У нас теперь есть парсер написаный на ANTLR, и мы можем подключить его к модулю для Netbeans. Для этого надо сгенерировать файлы lexer и parser в ANTLRWorks и добавить их в проект. Вы можете указать package для генерируемых файлов:

@header { package org.carbonfx.valaproject.antlr; }
@lexer::header { package org.carbonfx.valaproject.antlr; }


* This source code was highlighted with Source Code Highlighter.


2) Адаптируем анализатора ANTLR к API NetBeans


Во первых нужно реализовать абстрактный класс org.netbeans.spi.lexer.LanguageHierarchy. По сути этот класс определяет язык в виде токенов, токены делятся на категории. Категории необходимы как раз для реализации подсветки синтаксиса.

public class ValaLanguageHierarchy extends LanguageHierarchy<ValaTokenId> {

  @Override
  protected Collection<ValaTokenId> createTokenIds() {
    return Arrays.asList(tokens);
  }

  @Override
  protected Lexer<ValaTokenId> createLexer(LexerRestartInfo<ValaTokenId> lri) {
    return new org.carbonfx.valaproject.lexer.ValaLexer(lri);
  }

  @Override
  protected String mimeType() {
    return "text/x-vala";
  }

  static synchronized ValaTokenId getToken(int id) {
    return idToToken.get(id);
  }

  private static final Map<Integer, ValaTokenId> idToToken = new HashMap<Integer, ValaTokenId>();
  private static final ValaTokenId[] tokens = new ValaTokenId[] {
    token(ValaLexer.AND, "operator"),
    token(ValaLexer.AND_ASSIGN, "operator"),
    token(ValaLexer.BACKSLASH, "operator"),
// ... здесь определение всех токенов и соответствие категории
    token(ValaLexer.UNICODE_CHAR, "error"),
    token(ValaLexer.OTHER_CHAR, "error"),
    token(ValaLexer.UNKNOWN_CHAIN, "error"),
  };

  static {
    for (ValaTokenId token : tokens) {
      idToToken.put(token.ordinal(), token);
    }
  }

  private static ValaTokenId token(int antlrToken, String category) {
    return new ValaTokenId (ValaParser.tokenNames[antlrToken], category, antlrToken);
  }
}

* This source code was highlighted with Source Code Highlighter.


Реализацию ValaTokenId я здесь пропустил, она достаточно простая
Осталось создать класс ValaLexer, который реализует интерфейс Lexer<ValaTokenId>.

Основное предназначени класса ValaLexer — получение на вход последовательности символов и возврат последовательности токенов. В самой простой реализации достаточно в конструкторе сохранить последовательность символов и в методе nextToken() реализовать возврат токенов по очереди.

Еще один момент здесь заключается в том, что нужно адаптировать поток символов, которые нам отдает NetBeans Platform к потоку, который будут воспринимать классы ANTLR, а именно — к интерфейсу CharStream. К счастью уже есть готовая реализация этого класса в похожем проекте.

3) Конфигурация подсветки


Создаем файл FontAndColors.xml, в котором описываем соответствие категорий токенов (про это было выше), категориям подсвечиваемых разными цветами элементов. В частности наш файл выглядит следующим образом:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fontscolors PUBLIC "-//NetBeans//DTD Editor Fonts and Colors settings 1.1//EN" "http://www.netbeans.org/dtds/EditorFontsColors-1_1.dtd">
<fontscolors>
  <fontcolor name="character" default="character"/>
  <fontcolor name="error" default="error"/>
  <fontcolor name="identifier" default="identifier"/>
  <fontcolor name="keyword" default="keyword" />
  <fontcolor name="comment" default="comment"/>
  <fontcolor name="number" default="number"/>
  <fontcolor name="operator" default="operator"/>
  <fontcolor name="string" default="string"/>
  <fontcolor name="separator" default="separator"/>
  <fontcolor name="whitespace" default="whitespace"/>
  <fontcolor name="method" default="method" />
  <fontcolor name="javadoc" default="javadoc" foreColor="ff006666"/>
  <fontcolor name="regex" default="regex" foreColor="ff660066" />
  <fontcolor name="command" default="command" foreColor="ff006633" />
</fontscolors>

* This source code was highlighted with Source Code Highlighter.


В файле Bundle.properties описываем человеческие названия для категорий:
operator=Operator
string=String
separator=Separator
whitespace=Whitespace
method=Method
regex=Regular Expression
command=Command
...


* This source code was highlighted with Source Code Highlighter.


Далее добавляем в проект пример файла с исходным кодом ValaExample.vala. Он будет отображаться в диалоге настроек цвета для токенов NetBeans. И последний шаг — добавляем строки в файл layer.xml для подсветки:
<filesystem>
  <folder name="Editors">
    <folder name="text">
      <folder name="x-vala">
        <file name="language.instance">
          <attr name="instanceCreate" methodvalue="org.carbonfx.valaproject.lexer.ValaTokenId.getLanguage"/>
          <attr name="instanceOf" stringvalue="org.netbeans.api.lexer.Language"/>
        </file>
        <attr name="SystemFileSystem.localizingBundle" stringvalue="org.carbonfx.valaproject.Bundle"/>
        <folder name="FontsColors">
          <folder name="NetBeans">
            <folder name="Defaults">
              <file name="FontAndColors.xml" url="FontAndColors.xml">
                <attr name="SystemFileSystem.localizingBundle" stringvalue="org.carbonfx.valaproject.Bundle"/>
              </file>
            </folder>
          </folder>
        </folder>
      </folder>
    </folder>
  </folder>
  <folder name="OptionsDialog">
    <folder name="PreviewExamples">
      <folder name="text">
        <file name="x-vala" url="ValaExample.vala"/>
      </folder>
    </folder>
  </folder>
</filesystem>

* This source code was highlighted with Source Code Highlighter.


Если вы все правильно сделали, и я ничего не упустил — можете компилировать и запускать проект. При открытии файлов с расширением .vala мы видим что ключевые слова подсвечиваются нужным цветом. В диалоге настроек должен появиться язык Vala, который можно выбрать и посмотреть подсвеченное содержимое файла ValaExample.vala

Заключение


После всего проделанного, мы всего лишь получили подсветку синтаксиса. Но анализатор, который был написан с помощью ANTLR может использоваться и для других задач, а именно:
  1. проверка валидности кода и подсветка ошибочных конструкций;
  2. автоматическое формирование отступов;
  3. подсказка названий методов, аргументов функций.


На текущий день в самом плагине для Vala реализованы первые два пункта. В дальнейшем постараюсь описать, каким образом в проект можно добавить поддержку отступов и подсветки ошибочных конструкций.

Также была идея попробовать парсить код Vala не с использованием ANTLR, а с парсером, который сам написан на языке Vala. Если конкретно, то именно с его реализацией, который используется в компиляторе Vala — libvala. Если кому то интересно подключиться к доработке плагина, пишите!

Ссылки


  1. Исходный код проекта на github, там же можно скачать скомпилированный модуль
  2. Сайт ANTLR и ANTLRWorks
  3. Инструкция по написанию плагина для NetBeans + ANTLR
  4. Инструкция по написанию плагина для NetBeans + JavaCC

Tags:
Hubs:
+6
Comments 3
Comments Comments 3

Articles