- PVSM.RU - https://www.pvsm.ru -
Я начал писать статьи задолго до сегодняшних событий, и не уверен, что статьи еще могут быть актуальны (так как не уверен уже, что будет завтра), но не охота, чтобы усилия пропали совсем зря.
Дисклаймер: я не являюсь разработчиком из JetBrains, поэтому в статье и в коде могут быть и скорее всего есть неточности и ошибки.
Краткое оглавление
Часть 1
Введение [1]
Предусловия [2]
- Лексер
- Парсер
Аннотаторы [5]
Подсветка скобок [6]
Часть 2 [7]
Форматирование
Structure view
Кэши, индексы, stub и goto
Ссылки (Reference)
Find Usages
Rename и другие рефакторинги
Маркеры (Markers)
Автодополнение
Тестирование
Я работаю бекэнд-разработчиком и иногда устаю от перекладывания json из одного формата в другой (к сожалению, эта часть работы, хоть и не самая любимая). Как и любому программисту мне нравится копаться в коде, смотреть что как устроено и, возможно, использовать некоторые приемы в своей работе, а также мне нравится автоматизировать свою работу - в этом мне часто помогает IDEA.
Большая часть кода IDEA и особенно платформенная ее часть - open-source. Программисты из JetBrains активно помогают с pull request в основную ветку (спасибо @tagir_valeev [8] за помощь с парой инспекций). Один из главных плюсов IDEA (кроме богатого функционала): относительно легкое расширение плагинами. Примеры инспекции [9] и плагина для транзакций Spring [10] были рассмотрены в предыдущих статьях. Но для того чтобы понять, как работает основа IDEA, самое простое, как мне кажется, - это написать языковой плагин.
На habr уже была серия [11] замечательных статей по создания своего плагина, есть также официальная документация [12]. В этой серии статей я постарался не сильно повторять то, что уже было написано, а скорее углубленно расказать о интересных деталях и особенностях реализации.
Эти статьи могут быть интересны тем, кто хочет попробовать расширить IDEA под себя или добавить какой-нибудь интересный функционал, или просто тем, кому, как и мне, нравится копаться внутри.
В качестве "подопытного кролика", для которого будет написан плагин, взят максимально простой язык Monkey [13], создание интепретатора и компилятора для которого описано в книгах [14] по Golang. Так как у меня не было целью охватить все, то сам плагин охватывает некоторое его ограниченное подмножество. Сам интерпретатор можно найти тут [15].
Пример рассчета числа Фибоначчи на Monkey:
let fibonacci = fn(x){
if (x == 0){
0;
}
else{
if (x == 1){
return 1;
}
else{
fibonacci(x - 1) + fibonacci(x - 2);
};
};
};
Сам плагин будет писаться на Java и Kotlin (JVM), так как они являются основными языками разработки для платформы IDEA.
Самый просто способ создать любой плагин из IDEA - из шаблона [16]. Он активно развивается и включает в себя практически весь функционал, который нам нужен для разработки.
Для создания языкового плагина также понадобится DevKit. Он нужен для удобной настройки расширений (будут расмотрено ниже).
Для генерации лексера и парсера понадобится плагин Grammar-Kit [17].
Примеры языковых плагинов:
Java [18] (в основном коде IDEA)
go-plugin [19] (до того, как он стал платным и стал Golang. С ним есть некоторые сложности, так как многие зависимости устарели, но как пример хорош)
Haskell [20]
Erlang [21]
Frege [22] (его создание здорово описано его авторами в статьях [11])
Monkey plugin [23] (в рамках данной статьи)
Сразу отмечу, что Java plugin является самым развитым из всех, но при этом он сильно отличается от остальных, так как был первым. Например, go-plugin (здесь и далее для простоты он будет называться так, хотя репозиторий называется go-lang-idea-plugin [24], а пакет - goide) использует Grammar-Kit для создания парсера и лексера, парсер и лексер Java плагина же написаны полностью вручную.
Изучая исходный код IDEA, понимаешь, сколько в него вложено труда и знаний. Например, в IDEA для того, чтобы вычислять данные в debug окне, написан небольшой интерпретатор (спасибо Тагиру Валееву за твит [25] про это)
или еще один интерпретатор используется поточным анализом для выявления ошибок (его работа очень сильно напоминает работу самого Java интепретатора).
Первые этапы создания языкового плагина хорошо описаны в документации [26].
Требуется объявить новый язык (пример из go-plugin [27], frege [28], monkey [29])
import com.intellij.lang.Language
class MonkeyLanguage : Language("Monkey") {
companion object {
@JvmStatic
val INSTANCE = MonkeyLanguage()
}
}
Объявить иконку (пример из go-plugin [30], frege [31], monkey [32])
Объявить новый тип файла и связать все вместе (пример из go-plugin [33], frege [34], monkey [35])
import com.intellij.openapi.fileTypes.LanguageFileType
import javax.swing.Icon
class MonkeyFileType : LanguageFileType(MonkeyLanguage.INSTANCE) {
override fun getName(): String {
return "Monkey File"
}
override fun getDescription(): String {
return "Monkey language file"
}
override fun getDefaultExtension(): String {
return "monkey"
}
override fun getIcon(): Icon {
return MonkeyIcons.FILE
}
companion object {
@JvmStatic
val INSTANCE = MonkeyFileType()
}
}
После этого надо подключить новый тип файла через точку расширения (extension point). Все возможности, которые предоставляют плагины, подключаются через одну или несколько точек расширений. Они прописываются в файле plugin.xml (пример для go-plugin [36], frege [37]). Другие примеры использования точек расширений будут приведены ниже или можно посмотреть в документации [38].
<extensions defaultExtensionNs="com.intellij">
<fileType name="Monkey File"
implementationClass="com.github.pyltsin.monkeyplugin.MonkeyFileType"
fieldName="INSTANCE"
language="Monkey"
extensions="monkey"/>
</extensions>
К сожалению, в рамках одной статьи невозможно описать всю теорию, которая требуется для разбора кода. Фундаментальные знания по этой теме можно получить в "книге с драконом [39]"
Процесс работы компилятора с кодом состоит из следующих шагов:
Для успешной работы любой IDE требуется реализовать первые 3 анализатора:
Лексический анализатор (читает поток символов и группирует их в значащие последовательности, из которых строит токены)
Синтаксический анализатор (получает поток токенов и строит из них синтаксическое дерево - AST [40])
Семантический анализатор (использует дерево для проверки исходного кода программы на корректность языка).
Пример работы первых 3 анализаторов:
В IDEA вместо AST дерева используется аналог - PSI-дерево (Program structure Interface).
Процесс создания PSI-дерева хорошо показан на иллюстрации из документации [41]:
Для того чтобы его увидеть, можно воспользоваться PSI Viewer [42](Tools->View PSI Structure)
В IDEA для имплементации PSI-дерева используется в основном абстрактный класс TreeElement
public abstract class TreeElement extends ElementBase implements ASTNode, Cloneable {
private TreeElement myNextSibling;
private TreeElement myPrevSibling;
private CompositeElement myParent;
...
}
В IDEA для создания лексера и парсера можно использовать плагин GrammarKit [17].
Интересный кейс по созданию лексера описан в статье [11] про Frege.
Самый простой способ создания лексера для IDEA - использование JFlex [43]. Плагин GrammarKit содержит уже реализацию и позволяет генерить лексер или из .bnf файла (про него будет ниже) или из .flex файла (при этом больше возможностей для настройки). Пример для языка Monkey можно посмотреть здесь [44], более сложный для Frege - здесь [45].
Чтобы сгенерить сам Lexer, нужно или настроить Gradle плагин, или воспользоваться контестным меню в .flex файле - "Run JFlex Generator".
После этого нужно объявить класс, реализующий com.intellij.lexer.Lexer
. Для сгенерированного JFlex лексера уже существует адаптер - com.intellij.lexer.FlexAdapter
В IDEA для создания парсера в основном используется кодогенерация плагином GrammarKit [17]. К сожалению, документации по генерации парсера не так много и в основном она представлена в Tutorial [46] и HOWTO [47].
Грамматика языка описывается в виде BNF [48]. Единственное отличие, что используется ::=
как "является".
Взят отсюда [49]
{
generate=[psi="no"]
classHeader="//header.txt"
parserClass="org.intellij.grammar.expression.ExpressionParser"
extends(".*expr")=expr
elementTypeFactory="org.intellij.grammar.expression.ExpressionParserDefinition.createType"
tokenTypeFactory="org.intellij.grammar.expression.ExpressionParserDefinition.createTokenType"
elementTypeHolderClass="org.intellij.grammar.expression.ExpressionTypes"
parserUtilClass="org.intellij.grammar.parser.GeneratedParserUtilBase"
tokens=[
space='regexp:s+'
comment='regexp://.*'
number='regexp:d+(.d*)?'
id='regexp:p{Alpha}w*'
string="regexp:('([^'\]|\.)*'|"([^"\]|\.)*")"
syntax='regexp:;|.|+|-|**|*|==|=|/|,|(|)|^|!=|!|>=|<=|>|<'
]
}
root ::= element *
private element ::= expr ';'? {recoverWhile=element_recover}
private element_recover ::= !('(' | '+' | '-' | '!' | 'multiply' | id | number)
// left recursion and empty PSI children define expression root
expr ::= assign_expr
| conditional_group
| add_group
| boolean_group
| mul_group
| unary_group
| exp_expr
| factorial_expr
| call_expr
| qualification_expr
| primary_group
{extraRoot=true}
private boolean_group ::= xor_expr | between_expr | is_not_expr
private conditional_group ::= elvis_expr | conditional_expr
private unary_group ::= unary_plus_expr | unary_min_expr | unary_not_expr
private mul_group ::= mul_expr | div_expr
private add_group ::= plus_expr | minus_expr
private primary_group ::= special_expr | simple_ref_expr | literal_expr | paren_expr
// expressions: auto-operator detection or parens
fake ref_expr ::= expr? '.' identifier
simple_ref_expr ::= identifier {extends=ref_expr elementType=ref_expr}
qualification_expr ::= expr '.' identifier {extends=ref_expr elementType=ref_expr}
call_expr ::= ref_expr arg_list
arg_list ::= '(' [ !')' expr (',' expr) * ] ')' {pin(".*")=1}
literal_expr ::= number
identifier ::= id
unary_min_expr ::= '-' expr
unary_plus_expr ::= '+' expr
unary_not_expr ::= '!' expr
xor_expr ::= expr '^' expr
assign_expr ::= expr '=' expr { rightAssociative=true }
conditional_expr ::= expr ('<' | '>' | '<=' | '>=' | '==' | '!=') expr
div_expr ::= expr '/' expr
mul_expr ::= expr '*' expr
minus_expr ::= expr '-' expr
plus_expr ::= expr '+' expr
exp_expr ::= expr ('**' expr) + // N-ary variant
factorial_expr ::= expr '!'
paren_expr ::= '(' expr ')'
elvis_expr ::= expr '?' expr ':' expr
is_not_expr ::= expr IS NOT expr
between_expr ::= expr BETWEEN add_group AND add_group {
methods=[testExpr="expr[0]"]
}
// test specific expressions
external special_expr ::= meta_special_expr
meta_special_expr ::= 'multiply' '(' simple_ref_expr ',' mul_expr ')' {elementType="special_expr" pin=2}
Как видно, bnf файл состоит из 2 частей: первая часть описывает метаинформацию (и описание токенов, если не используется flex файлы), вторая часть описывает саму грамматику.
Рассмотрим некоторую часть метаинформации:
parserClass
- название и расположение генерируемого класса парсера
parserUtilClass
- ссылка на класс, содержащий набор вспомогательных методов для парсера (как правило, класс com.intellij.lang.parser.GeneratedParserUtilBase
или его наследник)
extends = <какой-то класс>
- ссылка на базовый класс, от которого будут наследоваться все PSI-элементы (узлы дерева). Обычно com.intellij.extapi.psi.ASTWrapperPsiElement
или его наследники.
extends(<regexp для узлов дерева>) = <psi-element>
(например: extends(".*expr")=expr
) - все psi-элементы будут наследоваться от указанного psi-элемента.
psiClassPrefix
, psiImplClassSuffix
- соответственно префикс классов и интерфейсов (обычно по имени языка) и суффикс для реализации интерфейсов (как правило - Impl)
psiPackage
и psiImplPackage
- соответственно пакет для интерфейсов и их реализаций.
implements
- аналогично extends, но для интерфейсов
elementTypeHolderClass
- генерируемое хранилище всех типов элементов
elementTypeClass
- класс типов элеметов (не генерируется, наследник com.intellij.psi.tree.IElementType
)
elementTypeFactory
- создание фабрики для генерации типов элементов (используется для работы со Stub - о них ниже)
psiImplUtilClass
- класс с набором статических методов, которые используются как имплементация требуемых методов для psi-элементов. Предположим, у нас есть такие строчки (из go-plugin)
ImportSpec ::= [ '.' | identifier ] ImportString {
stubClass="com.goide.stubs.GoImportSpecStub"
methods=[getAlias getLocalPackageName shouldGoDeeper isForSideEffects isDot getPath getName isCImport]
}
Для ImportSpec должен быть сгенерирован метод getAlias. Для этого в psiImplUtilClass
должен быть объявлен соответствующий метод
public static String getAlias(@NotNull GoImportSpec importSpec)
а в самом классе будет просто вызов этого метода
public String getAlias() {
return GoPsiImplUtil.getAlias(this);
}
Теперь перейдем к самим bnf правилам. Для каждого правила могут быть использованы модификаторы (например, private
, fake
и так далее). Их описание приведено здесь [50]. Так например private в
private boolean_group ::= xor_expr | between_expr | is_not_expr
говорит о том, что PSI-элемент для boolean_group
сгенерирован не будет.
Если не получается правильно описать грамматику в bnf файле, то есть возможность описать это в коде, используя внешние правила [51].
Одна из важных частей грамматики - правила работы с ошибками. Для этого используется два ключевых слова: pin
, recoverWhile
.
pin
- указывает номер токена, как только мы доходим до которого, парсер начинает ожидать только текущее объявление. Например, объявление структуры в Golang
StructType ::= struct '{' Fields? '}' {pin=1}
recoverWhile
- указывает, какие токены можно потреблять после завершения сопоставления со всеми правилами. Рекомендации по применению этого атрибута описаны здесь [52].
Также следует обратить внимание на рекомендации [53] для парсинга выражений с учетом приоритета.
Как мне кажется, создание правильного и удобного описания грамматики для будущей работы - одна из самых сложных частей реализации плагина для языка. Чтобы начать, можно ориентироваться на примеры: go-plugin [54], Frege [55], Monkey [56] (для Monkey с целью упрощения реализовано только подмножество этого языка).
После создания bnf файла и генерации из него парсера требуется определить класс файла (наследник от com.intellij.extapi.psi.PsiFileBase
) (пример go-plugin [57], Frege [58], Monkey [59]) и класс определения парсера (наследник от com.intellij.lang.ParserDefinition)
(пример go-plugin [60], Frege [61], Monkey [62]), и после этого подключить его через точку расширения.
<lang.parserDefinition language="Monkey"
implementationClass="com.github.pyltsin.monkeyplugin.parser.MonkeyParserDefinition"/>
В предыдущих частях мы посмотрели как создаются и работают лексер и парсер, которые отвечают, соответственно, за лексический и синтаксический анализ. Теперь перейдем к третьей части - семантический анализ. Изучая код IDEA и плагинов к ней, я нашел два способа его реализации (исключая инспекции).
Первый способ применен в плагине для языка Java. Рассмотрим следующий невалидный код:
IDEA, конечно, его подсветила и сказала "Operator '-' cannot be applied to 'java.lang.String', 'java.lang.String'". Это работает благодаря следующей точки расширения:
<highlightVisitor implementation=
"com.intellij.codeInsight.daemon.impl.analysis.HighlightVisitorImpl"/>
Сам класс должен реализовывать интерфейс com.intellij.codeInsight.daemon.impl.HighlightVisitor
public interface HighlightVisitor {
boolean suitableForFile(@NotNull PsiFile file);
void visit(@NotNull PsiElement element);
boolean analyze(@NotNull PsiFile file,
boolean updateWholeFile,
@NotNull HighlightInfoHolder holder,
@NotNull Runnable action);
}
Метод analyze
- используется для настройки, запуска подсветки (action.run()
) и очистки ресурсов.
Метод visit выполняется при вызове action.run()
и выполняет сам анализ.
//Реализация из HighlightVisitorImpl
@Override
public void visit(@NotNull PsiElement element) {
// некоторый код
element.accept(this);
// некоторый код
}
//Пример для класса ClsJavaModuleImpl, реализация accept
@Override
public void accept(@NotNull PsiElementVisitor visitor) {
if (visitor instanceof JavaElementVisitor) {
((JavaElementVisitor)visitor).visitModule(this);
}
else {
visitor.visitElement(this);
}
}
Как видно, здесь используется паттерн visitor [63]. Сам класс HighlightVisitorImpl
также расширяет JavaElementVisitor
.
public abstract class JavaElementVisitor extends PsiElementVisitor {
public void visitAnonymousClass(PsiAnonymousClass aClass) {
visitClass(aClass);
}
public void visitArrayAccessExpression(PsiArrayAccessExpression expression) {
visitExpression(expression);
}
public void visitArrayInitializerExpression(PsiArrayInitializerExpression expression) {
visitExpression(expression);
}
//и еще много-много методов для каждого типа PSI-элемента
Второй способ применен в плагине go-plugin и Frege. В плагине Monkey я использовал тоже его. Он заключается в использовании точки расширения annotator
Подключение:
<annotator language="Monkey"
implementationClass="com.github.pyltsin.monkeyplugin.annotator.MonkeyWarningAnnotator"/>
Класс должен реализовывать интерфейс:
public interface Annotator {
void annotate(@NotNull PsiElement element,
@NotNull AnnotationHolder holder);
}
Само сообщение об ошибке регистрируется следующим образом:
holder.newAnnotation(HighlightSeverity.ERROR, errorMsg)
.range(element)
.create()
Примеры для Frege [64], go-plugin [65], Monkey [66].
Для языка Monkey на данный момент реализовал 2 проверки - невозможность разрешить ссылки (resolve references - о них ниже) и простая проверка типов элементов (через DSL [67]).
В этой части мы рассмотрим еще пару точек расширений.
Первая точка расширения: lang.braceMatcher
. Пример подключения:
<lang.braceMatcher language="Monkey"
implementationClass="com.github.pyltsin.monkeyplugin.editor.MonkeyBraceMatcher"/>
Эта точка расширения включает подсветку пары скобок и добавление закрывающей скобки
Класс должен реализовывать интерфейс com.intellij.lang.PairedBraceMatcher
public interface PairedBraceMatcher {
/**
* Returns the array of definitions for brace pairs that need to be matched when
* editing code in the language.
*
* @return the array of brace pair definitions.
*/
@NotNull
BracePair[] getPairs();
/**
* Returns true if paired rbrace should be inserted after lbrace of given type when lbrace is encountered before contextType token.
* It is safe to always return true, then paired brace will be inserted anyway.
* @param lbraceType lbrace for which information is queried
* @param contextType token type that follows lbrace
* @return true / false as described
*/
boolean isPairedBracesAllowedBeforeType(@NotNull IElementType lbraceType, @Nullable IElementType contextType);
/**
* Returns the start offset of the code construct which owns the opening structural brace at the specified offset. For example,
* if the opening brace belongs to an 'if' statement, returns the start offset of the 'if' statement.
*
* @param file the file in which brace matching is performed.
* @param openingBraceOffset the offset of an opening structural brace.
* @return the offset of corresponding code construct, or the same offset if not defined.
*/
int getCodeConstructStart(final PsiFile file, int openingBraceOffset);
}
Релизация, которая была сделана мной для языка Monkey, можно посмотреть тут [68], для плагина go-plugin тут [69], для Java - тут [70] и тут [71].
Вторая точка расширения: highlightVisitor
. Я ее уже упоминал для создания семантического анализатора. В своем плагине я ее не использовал, но она используется в популярном плагине Rainbow Brackets [72], который раскрашивает пары скобок в уникальные цвета.
Если посмотреть в его plugin.xml, то можно найти вот такую строчку
<highlightVisitor implementation="com.github.izhangzhihao.rainbow.brackets.visitor.DefaultRainbowVisitor"/>
Класс реализует интерфейс - com.intellij.codeInsight.daemon.impl.HighlightVisitor
. Реализацию можно посмотреть здесь [73]. Само раскрашивание происходит в методе com.github.izhangzhihao.rainbow.brackets.visitor.RainbowHighlightVisitor#setHighlightInfo
holder.add(HighlightInfo
.newHighlightInfo(rainbowElement)
.textAttributes(attr)
.range(element)
.create())
Продолжение тут [7].
Автор:
pyltsinm
Источник [74]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/372815
Ссылки в тексте:
[1] Введение: #introduction
[2] Предусловия: #require
[3] Создание основы языкового плагина: #base
[4] Создание PSI-дерева: #AST
[5] Аннотаторы: #annotators
[6] Подсветка скобок: #brackets
[7] Часть 2: https://habr.com/ru/post/653667/
[8] @tagir_valeev: https://www.pvsm.ru/users/tagir_valeev
[9] инспекции: https://habr.com/ru/post/546992/
[10] плагина для транзакций Spring: https://habr.com/ru/post/552002/
[11] серия: https://habr.com/ru/company/hsespb/blog/574692/
[12] официальная документация: https://plugins.jetbrains.com/docs/intellij/custom-language-support-tutorial.html
[13] Monkey: https://monkeylang.org/
[14] книгах: https://interpreterbook.com/
[15] тут: https://github.com/pyltsin/monkey-source
[16] шаблона: https://github.com/JetBrains/intellij-platform-plugin-template
[17] Grammar-Kit: https://plugins.jetbrains.com/plugin/6606-grammar-kit
[18] Java: https://github.com/JetBrains/intellij-community
[19] go-plugin: https://github.com/go-lang-plugin-org/go-lang-idea-plugin.git
[20] Haskell: https://github.com/carymrobbins/intellij-haskforce
[21] Erlang: https://github.com/ignatov/intellij-erlang/blob/master/grammars/erlang.bnf
[22] Frege: https://github.com/IntelliJ-Frege
[23] Monkey plugin: https://github.com/pyltsin/monkey-plugin
[24] go-lang-idea-plugin: https://github.com/go-lang-plugin-org/go-lang-idea-plugin
[25] твит: https://twitter.com/tagir_valeev/status/1360512507744550914
[26] документации: https://plugins.jetbrains.com/docs/intellij/language-and-filetype.html
[27] go-plugin: https://github.com/go-lang-plugin-org/go-lang-idea-plugin/blob/master/src/com/goide/GoLanguage.java
[28] frege: https://github.com/IntelliJ-Frege/intellij-frege/blob/master/src/main/java/com/plugin/frege/FregeLanguage.java
[29] monkey: https://github.com/pyltsin/monkey-plugin/blob/main/src/main/kotlin/com/github/pyltsin/monkeyplugin/MonkeyLanguage.kt
[30] go-plugin: https://github.com/go-lang-plugin-org/go-lang-idea-plugin/blob/master/src/com/goide/GoIcons.java
[31] frege: https://github.com/IntelliJ-Frege/intellij-frege/blob/master/src/main/java/com/plugin/frege/FregeIcons.java
[32] monkey: https://github.com/pyltsin/monkey-plugin/blob/main/src/main/kotlin/com/github/pyltsin/monkeyplugin/MonkeyIcons.kt
[33] go-plugin: https://github.com/go-lang-plugin-org/go-lang-idea-plugin/blob/master/src/com/goide/GoFileType.java
[34] frege: https://github.com/IntelliJ-Frege/intellij-frege/blob/master/src/main/java/com/plugin/frege/FregeFileType.java
[35] monkey: https://github.com/pyltsin/monkey-plugin/blob/main/src/main/kotlin/com/github/pyltsin/monkeyplugin/MonkeyFileType.kt
[36] go-plugin: https://github.com/go-lang-plugin-org/go-lang-idea-plugin/blob/master/resources/META-INF/gogland.xml
[37] frege: https://github.com/IntelliJ-Frege/intellij-frege/blob/master/src/main/resources/META-INF/plugin.xml
[38] документации: https://plugins.jetbrains.com/docs/intellij/welcome.html
[39] книге с драконом: https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BC%D0%BF%D0%B8%D0%BB%D1%8F%D1%82%D0%BE%D1%80%D1%8B:_%D0%BF%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF%D1%8B,_%D1%82%D0%B5%D1%85%D0%BD%D0%BE%D0%BB%D0%BE%D0%B3%D0%B8%D0%B8_%D0%B8_%D0%B8%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D1%8B
[40] AST: https://en.wikipedia.org/wiki/Abstract_syntax_tree
[41] документации: https://plugins.jetbrains.com/docs/intellij/implementing-parser-and-psi.html
[42] PSI Viewer : https://www.jetbrains.com/help/idea/psi-viewer.html
[43] JFlex: https://jflex.de/
[44] здесь: https://github.com/pyltsin/monkey-plugin/blob/main/src/main/java/com/github/pyltsin/monkeyplugin/Monkey.flex
[45] здесь: https://github.com/IntelliJ-Frege/intellij-frege/blob/master/src/main/java/com/plugin/frege/lexer/FregeLexer.flex
[46] Tutorial: https://github.com/JetBrains/Grammar-Kit/blob/master/TUTORIAL.md
[47] HOWTO: https://github.com/JetBrains/Grammar-Kit/blob/master/HOWTO.md
[48] BNF: https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form
[49] отсюда: https://github.com/JetBrains/Grammar-Kit/blob/master/testData/generator/ExprParser.bnf
[50] здесь: https://github.com/JetBrains/Grammar-Kit#rule-modifiers
[51] внешние правила: https://github.com/JetBrains/Grammar-Kit/blob/master/HOWTO.md#23-when-nothing-helps-external-rules
[52] здесь: https://github.com/JetBrains/Grammar-Kit/blob/master/HOWTO.md#22-using-recoverwhile-attribute
[53] рекомендации: https://github.com/JetBrains/Grammar-Kit/blob/master/HOWTO.md#24-compact-expression-parsing-with-priorities
[54] go-plugin: https://github.com/go-lang-plugin-org/go-lang-idea-plugin/blob/master/grammars/go.bnf
[55] Frege: https://github.com/IntelliJ-Frege/intellij-frege/blob/master/src/main/java/com/plugin/frege/Frege.bnf
[56] Monkey: https://github.com/pyltsin/monkey-plugin/blob/main/src/main/java/com/github/pyltsin/monkeyplugin/Monkey.bnf
[57] go-plugin: https://github.com/go-lang-plugin-org/go-lang-idea-plugin/blob/master/src/com/goide/psi/GoFile.java
[58] Frege: https://github.com/IntelliJ-Frege/intellij-frege/blob/master/src/main/kotlin/com/plugin/frege/psi/FregeFile.kt
[59] Monkey: https://github.com/pyltsin/monkey-plugin/blob/main/src/main/kotlin/com/github/pyltsin/monkeyplugin/psi/MonkeyFile.kt
[60] go-plugin: https://github.com/go-lang-plugin-org/go-lang-idea-plugin/blob/master/src/com/goide/GoParserDefinition.java
[61] Frege: https://github.com/IntelliJ-Frege/intellij-frege/blob/master/src/main/java/com/plugin/frege/parser/FregeParserDefinition.java
[62] Monkey: https://github.com/pyltsin/monkey-plugin/blob/main/src/main/kotlin/com/github/pyltsin/monkeyplugin/parser/MonkeyParserDefinition.kt
[63] visitor: https://refactoring.guru/ru/design-patterns/visitor
[64] Frege: https://github.com/IntelliJ-Frege/intellij-frege/blob/master/src/main/kotlin/com/plugin/frege/annotator/FregeWarningAnnotator.kt
[65] go-plugin: https://github.com/go-lang-plugin-org/go-lang-idea-plugin/blob/master/src/com/goide/highlighting/GoAnnotator.java
[66] Monkey: https://github.com/pyltsin/monkey-plugin/blob/main/src/main/kotlin/com/github/pyltsin/monkeyplugin/annotator/MonkeyWarningAnnotator.kt
[67] DSL: https://github.com/pyltsin/monkey-plugin/blob/main/src/main/kotlin/com/github/pyltsin/monkeyplugin/psi/impl/TypeResolver.kt
[68] тут: https://github.com/pyltsin/monkey-plugin/blob/main/src/main/kotlin/com/github/pyltsin/monkeyplugin/editor/MonkeyBraceMatcher.kt
[69] тут: https://github.com/go-lang-plugin-org/go-lang-idea-plugin/blob/master/src/com/goide/editor/GoBraceMatcher.java
[70] тут: https://github.com/JetBrains/intellij-community/blob/master/java/java-impl/src/com/intellij/codeInsight/highlighting/JavaBraceMatcher.java
[71] тут: https://github.com/JetBrains/intellij-community/blob/master/java/java-impl/src/com/intellij/codeInsight/highlighting/JavaPairedBraceMatcher.java
[72] Rainbow Brackets: https://plugins.jetbrains.com/plugin/10080-rainbow-brackets
[73] здесь: https://github.com/izhangzhihao/intellij-rainbow-brackets/blob/2020.3/src/main/kotlin/com/github/izhangzhihao/rainbow/brackets/visitor/DefaultRainbowVisitor.kt
[74] Источник: https://habr.com/ru/post/653183/?utm_source=habrahabr&utm_medium=rss&utm_campaign=653183
Нажмите здесь для печати.