使用antlr生成语法树_如何使用ANTLR和Kotlin为您的语言创建带有语法高亮显示的编辑器...

本文展示了如何使用ANTLR和Kotlin创建一个独立的编辑器,该编辑器能够对自定义语言进行语法高亮显示。通过修改ANTLR词法分析器,将忽略的标记改为捕获,以实现语法高亮。示例代码使用Gradle构建,支持Python和Sandy语言的语法突出显示。编辑器基于RSyntaxTextArea组件,未来计划扩展为支持更多语言。
摘要由CSDN通过智能技术生成

使用antlr生成语法树

我们将要建立的

在本文中,我们将看到如何构建一个突出显示我们语言的语法的独立编辑器。 语法高亮功能将基于我们在第一篇文章中构建的ANTLR lexer。 该代码将在Kotlin中使用,但是应该可以轻松转换为Java。 该编辑器将命名为Kanvas

屏幕截图

以前的帖子

这篇文章是有关如何创建有用的语言和所有支持工具的系列文章的一部分。

  1. 建立一个词法分析器
  2. 建立一个解析器

代码在GitHub上可用 。 这篇文章中描述的代码与标签语法_highlighting相关联

建立

我们将使用Gradle作为构建系统。

buildscript {
   ext.kotlin_version = '1.0.3'
 
   repositories {
     mavenCentral()
     maven {
        name 'JFrog OSS snapshot repo'
        url  'https://oss.jfrog.org/oss-snapshot-local/'
     }
     jcenter()
   }
 
   dependencies {
     classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
   }
}
 
apply plugin: 'kotlin'
apply plugin: 'application'
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'antlr'
 
repositories {
  mavenLocal()
  mavenCentral()
  jcenter()
}
 
dependencies {
  antlr "org.antlr:antlr4:4.5.1"
  compile "org.antlr:antlr4-runtime:4.5.1"
  compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
  compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
  compile 'com.fifesoft:rsyntaxtextarea:2.5.8'
  compile 'me.tomassetti:antlr-plus:0.1.1'
  testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
  testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
}
 
mainClassName = "KanvasKt"
 
generateGrammarSource {
    maxHeapSize = "64m"
    arguments += ['-package', 'me.tomassetti.lexers']
    outputDirectory = new File("generated-src/antlr/main/me/tomassetti/lexers".toString())
}
compileJava.dependsOn generateGrammarSource
sourceSets {
    generated {
        java.srcDir 'generated-src/antlr/main/'
    }
}
compileJava.source sourceSets.generated.java, sourceSets.main.java
 
clean {
    delete "generated-src"
}
 
idea {
    module {
        sourceDirs += file("generated-src/antlr/main")
    }
}

这应该很简单。 一些评论:

  • 我们的编辑器将基于RSyntaxTextArea组件,因此我们添加了相应的依赖项
  • 我们正在指定我们使用的Kotlin版本。 您无需在系统上安装Kotlin:Gradle将下载编译器并根据此配置使用它
  • 我们正在使用ANTLR从我们的词法分析器语法生成词法分析器
  • 我们使用的是idea插件:通过运行./gradlew idea我们可以生成一个IDEA项目。 请注意,我们将生成的代码添加到源目录列表中
  • 当我们运行./gradlew clean时,我们还要删除生成的代码

对ANTLR词法分析器的更改

当使用词法分析器提供解析器时,我们可以安全地忽略一些标记。 例如,我们可能想忽略空格和注释。 当使用词法分析器来突出显示语法时,我们希望捕获所有可能的标记。 因此,我们需要对第一篇文章中定义的词法分析器语法稍作调整,方法是:

  • 定义第二个通道 ,我们将在其中发送对解析器无用但语法突出显示所需的令牌
  • 我们将添加一个新的实名UNMATCHED,以捕获不属于正确标记的所有字符。 这是必要的,因为需要在编辑器中显示无效令牌,并且因为键入时用户会一直临时引入无效令牌。

解析器将仅考虑默认通道中的标记,而我们将在语法荧光笔中使用默认通道和EXTRA通道。

这就是结果语法。

lexer grammar SandyLexer;
 
@lexer::members {
    private static final int EXTRA = 1;
}
 
 
// Whitespace
NEWLINE            : '\r\n' | 'r' | '\n' ;
WS                 : [\t ]+ -> channel(EXTRA) ;
 
// Keywords
VAR                : 'var' ;
 
// Literals
INTLIT             : '0'|[1-9][0-9]* ;
DECLIT             : '0'|[1-9][0-9]* '.' [0-9]+ ;
 
// Operators
PLUS               : '+' ;
MINUS              : '-' ;
ASTERISK           : '*' ;
DIVISION           : '/' ;
ASSIGN             : '=' ;
LPAREN             : '(' ;
RPAREN             : ')' ;
 
// Identifiers
ID                 : [_]*[a-z][A-Za-z0-9_]* ;
 
UNMATCHED          : .  -> channel(EXTRA);

我们的编辑

我们的编辑器将支持多种语言的语法突出显示。 目前,我已经实现了对Python和Sandy(我们在本系列文章中正在使用的简单语言)的支持。 为了实现Python的语法突出显示,我从Python的ANTLR语法开始。 我从删除解析器规则开始,仅保留词法分析器规则。 然后,U进行了非常小的更改,以使最终词法分析器出现在存储库中。

要创建GUI,我们将需要一些非常无聊的Swing代码。 我们基本上需要创建一个带有菜单的框架。 框架将包含一个选项卡式面板。 每个文件一个标签。 在每个选项卡中,我们只有一个使用RSyntaxTextArea实现的编辑器。

fun createAndShowKanvasGUI() {
    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
 
    val xToolkit = Toolkit.getDefaultToolkit()
    val awtAppClassNameField = xToolkit.javaClass.getDeclaredField("awtAppClassName")
    awtAppClassNameField.isAccessible = true
    awtAppClassNameField.set(xToolkit, APP_TITLE)
    
    val frame = JFrame(APP_TITLE)
    frame.background = BACKGROUND_DARKER
    frame.contentPane.background = BACKGROUND_DARKER
    frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE
 
    val tabbedPane = MyTabbedPane()
    frame.contentPane.add(tabbedPane)
 
    val menuBar = JMenuBar()
    val fileMenu = JMenu("File")
    menuBar.add(fileMenu)
    val open = JMenuItem("Open")
    open.addActionListener { openCommand(tabbedPane) }
    fileMenu.add(open)
    val new = JMenuItem("New")
    new.addActionListener { addTab(tabbedPane, "<UNNAMED>", font) }
    fileMenu.add(new)
    val save = JMenuItem("Save")
    save.addActionListener { saveCommand(tabbedPane) }
    fileMenu.add(save)
    val saveAs = JMenuItem("Save as")
    saveAs.addActionListener { saveAsCommand(tabbedPane) }
    fileMenu.add(saveAs)
    val close = JMenuItem("Close")
    close.addActionListener { closeCommand(tabbedPane) }
    fileMenu.add(close)
    frame.jMenuBar = menuBar
 
    frame.pack()
    if (frame.width < 500) {
        frame.size = Dimension(500, 500)
    }
    frame.isVisible = true
}
 
fun main(args: Array<String>) {
    languageSupportRegistry.register("py", pythonLanguageSupport)
    languageSupportRegistry.register("sandy", sandyLanguageSupport)
    SwingUtilities.invokeLater { createAndShowKanvasGUI() }
}

请注意,在主要功能中,我们注册了对两种语言的支持。 将来我们可以设想一个可插拔的系统来支持更多的语言。

对Sandy的特定支持在对象sandyLanguageSupport中定义(对于Java开发人员:对象只是类的单例实例)。 支持需要SyntaxSchemeAntlrLexerFactory

SyntaxScheme只是返回每种给定令牌类型的样式。 很容易,是吗?

object sandySyntaxScheme : SyntaxScheme(true) {
    override fun getStyle(index: Int): Style {
        val style = Style()
        val color = when (index) {
            SandyLexer.VAR -> Color.GREEN
            SandyLexer.ASSIGN -> Color.GREEN
            SandyLexer.ASTERISK, SandyLexer.DIVISION, SandyLexer.PLUS, SandyLexer.MINUS -> Color.WHITE
            SandyLexer.INTLIT, SandyLexer.DECLIT -> Color.BLUE
            SandyLexer.UNMATCHED -> Color.RED
            SandyLexer.ID -> Color.MAGENTA
            SandyLexer.LPAREN, SandyLexer.RPAREN -> Color.WHITE
            else -> null
        }
        if (color != null) {
            style.foreground = color
        }
        return style
    }
}
 
object sandyLanguageSupport : LanguageSupport {
    override val syntaxScheme: SyntaxScheme
        get() = sandySyntaxScheme
    override val antlrLexerFactory: AntlrLexerFactory
        get() = object : AntlrLexerFactory {
            override fun create(code: String): Lexer = SandyLexer(org.antlr.v4.runtime.ANTLRInputStream(code))
        }
}

现在,让我们看一下AntlrLexerFactory 。 该工厂仅实例化特定语言的ANTLR Lexer。 它可以与AntlrTokenMaker一起使用。 AntlrTokenMaker是ANTLR LexerRSyntaxTextArea用于处理文本的TokenMaker之间的适配器。 基本上,随着行的更改,将分别为文件的每一行调用TokenMakerBase。 因此,我们要求ANTLR Lexer仅处理该行,然后获得生成的令牌并实例化RSyntaxTextArea期望的TokenImpl实例。

interface AntlrLexerFactory {
    fun create(code: String) : Lexer
}
 
class AntlrTokenMaker(val antlrLexerFactory : AntlrLexerFactory) : TokenMakerBase() {
 
    fun toList(text: Segment, startOffset: Int, antlrTokens:List<org.antlr.v4.runtime.Token>) : Token?{
        if (antlrTokens.isEmpty()) {
            return null
        } else {
            val at = antlrTokens[0]
            val t = TokenImpl(text, text.offset + at.startIndex, text.offset + at.startIndex + at.text.length - 1, startOffset + at.startIndex, at.type, 0)
            t.nextToken = toList(text, startOffset, antlrTokens.subList(1, antlrTokens.size))
            return t
        }
    }
 
    override fun getTokenList(text: Segment?, initialTokenType: Int, startOffset: Int): Token {
        if (text == null) {
            throw IllegalArgumentException()
        }
        val lexer = antlrLexerFactory.create(text.toString())
        val tokens = LinkedList<org.antlr.v4.runtime.Token>()
        while (!lexer._hitEOF) {
            tokens.add(lexer.nextToken())
        }
        return toList(text, startOffset, tokens) as Token
    }
 
}

对于跨越多行的令牌来说,这将是一个问题。 有一些解决方法,但是鉴于桑迪没有跨越多行的令牌,现在让我们保持简单,不要考虑这一点。

结论

当然,此编辑器功能不完整。 但是,我认为有趣的是,如何用几百行非常简单的Kotlin代码构建具有语法高亮显示的编辑器。

在以后的文章中,我们将看到如何丰富此编辑器的功能,例如自动完成功能。

翻译自: https://www.javacodegeeks.com/2016/08/create-editor-syntax-highlighting-language-using-antlr-kotlin.html

使用antlr生成语法树

03-30 1万+
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值