AI-generated image
Development

Wie man eine eigene Sprache in die JetBrains IDE bringt

2 Min. Lesezeit

Ich brauchte Freemarker-Support in IntelliJ und es gab keinen. Das eingebaute Highlighting war zur Hälfte falsch, Completion gab es nicht, und zwischen Templates navigieren war Ratespiel. Also habe ich ein Plugin geschrieben. Das war Plugin Nummer eins. Einundzwanzig weitere folgten.

Um eine eigene Sprache in eine JetBrains IDE zu bringen, braucht man vier Dinge: einen Lexer, einen Parser, einen PSI-Baum und mindestens ein nützliches Feature obendrauf. Alles andere ist optional, macht das Plugin aber erst richtig brauchbar.

Anfang mit BNF

JetBrains stellt Grammar-Kit bereit, einen BNF-basierten Parser-Generator, der die PSI-Baumstruktur erzeugt. Man schreibt die Grammatik in einer .bnf-Datei, Grammar-Kit generiert den Parser und die Element-Typen. Der generierte Code ist ausführlich, aber korrekt.

Der Lexer ist eine JFlex-Datei. Er zerlegt den Quellcode in Keywords, Identifier, Strings, Kommentare. Grammar-Kit konsumiert diese Tokens und baut den Baum. Dieser Teil frisst ungefähr 80% des initialen Aufwands - die Grammatik für eine Sprache richtig zu schreiben, die man nicht selbst entworfen hat, ist schwieriger als es klingt.

Für Flexible Freemarker musste ich eine Template-Sprache in HTML eingebettet verarbeiten. Zwei Grammatiken, verschränkt. Das war der Moment, in dem mir klar wurde, dass diese Infrastruktur wiederverwendbar sein muss.

PSI ist das Fundament

Der PSI-Baum (Program Structure Interface) ist der abstrakte Syntaxbaum, den IntelliJ für alles benutzt. Jedes Feature - Completion, Navigation, Refactoring, Inspections - liest von diesem Baum. Wenn die PSI-Struktur falsch ist, ist jedes Feature darüber auch falsch.

PSI zuerst richtig machen. Features danach. Ich habe den Fehler gemacht, Completion zu bauen, bevor der Baum stabil war, und habe es dreimal umgeschrieben.

Was ein Plugin nützlich macht

Syntax-Highlighting bringt Downloads. Code-Completion bringt zahlende Nutzer. Nach meiner Erfahrung ist die Feature-Priorität: Highlighting, Completion, Navigation (Go-to-Definition, Find-Usages), dann Inspections (Warnungen und Quick-Fixes).

Bei Inspections wird es interessant. Für Flexible Solidity habe ich Reentrancy-Guard-Inspections geschrieben, die eine Klasse von Smart-Contract-Bugs schon beim Tippen finden. Das kann ein generischer Linter nicht, weil er den vollständigen PSI-Baum braucht.

Was einem keiner vorher sagt

Die IntelliJ Platform API ist mächtig, aber vielleicht zu 30% dokumentiert. Man verbringt mehr Zeit im IntelliJ Community Edition Quellcode als in der Dokumentation. JetBrains wird da besser, langsam. Aber “lies den Source” ist immer noch die echte Dokumentation für alles jenseits der Basics.

Wer es probieren will - fangt mit einer Sprache an, die ihr selbst benutzt. Das erste Plugin sind drei Monate. Das zweite drei Wochen. Danach ist es ein Wochenende pro Sprache, wenn die Infrastruktur steht. Wie diese gemeinsame Codebasis funktioniert, habe ich in One Grammar Engine beschrieben.