ANTLR入门:构建一种简单的表达语言

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

  1. 建立词法分析器
  2. 建立一个解析器
  3. 创建带有语法突出显示的编辑器
  4. 使用自动补全功能构建编辑器
  5. 将解析树映射到抽象语法树
  6. 模型转换模型
  7. 验证方式
  8. 生成字节码

写完这一系列文章后,我改进了方法,进行了扩展,并澄清为《 如何创建实用的轻量级语言》一书。

在本文中,我们将开始研究一种非常简单的表达语言。 我们将在语言沙箱中构建它,因此我们将其称为语言Sandy

我认为工具支持对于一种语言至关重要:因此,我们将从一种非常简单的语言开始,但是我们将为此提供丰富的工具支持。 要从一种语言中受益,我们需要解析器,解释器和编译器,编辑器等。 在我看来,构建简单的解析器的材料很多,但是构建使用语言的实用有效所需的其余基础结构的材料却很少。

我想专注于这些方面,使语言小巧但完全有用。 然后,您将能够有机地增长语言。

该代码可在GitHub上找到: https : //github.com/ftomassetti/LangSandbox 。 本文中提供的代码对应于标记01_lexer。

语言

该语言将允许定义变量和表达式。 我们将支持:

  • 整数和十进制文字
  • 变量定义和赋值
  • 基本数学运算(加法,减法,乘法,除法)
  • 括号的用法

有效文件的示例:

 var a = 10 / 3  var b = ( 5 + 3 ) * 2  var c = a / b 

我们将使用的工具

我们将使用:

  • ANTLR生成词法分析器和解析器
  • 使用Gradle作为我们的构建系统
  • 用Kotlin编写代码。 鉴于我刚开始学习,这将是非常基本的Kotlin。

设置项目

我们的构建。 gradle文件将如下所示

 buildscript { 
    ext.kotlin_version = '1.3.70' 
    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: 'java'  apply plugin: 'idea'  apply plugin: 'antlr'  repositories { 
   mavenLocal() 
   mavenCentral() 
   jcenter()  }  dependencies { 
   antlr "org.antlr:antlr4:4.8" 
   compile "org.antlr:antlr4-runtime:4.8" 
   compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 
   compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" 
   testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" 
   testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" 
   testCompile 'junit:junit:4.13'  }  generateGrammarSource { 
     maxHeapSize = "64m" 
     arguments += [ '-package' , 'me.tomassetti.langsandbox' ] 
     outputDirectory = new File( "generated-src/antlr/main/me/tomassetti/langsandbox" .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" ) 
     }  } 

我们可以运行:

  • ./gradlew想法来生成IDEA项目文件
  • ./gradlew generateGrammarSource生成ANTLR词法分析器和解析器

实施词法分析器

我们将在两个单独的文件中构建词法分析器和解析器。 这是词法分析器:

 lexer grammar SandyLexer;  // Whitespace  NEWLINE           : '\r\n' | 'r' | '\n' ;  WS                : [\t ]+ ;  // 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                : [_]*[az][A-Za-z0-9_]* ; 

现在,我们可以简单地运行./ gradlew generateGrammarSource ,然后将根据先前的定义为我们生成词法分析器。

测试词法分析器

测试始终很重要,但是在构建语言时绝对至关重要:如果支持您的语言的工具不正确,这可能会影响您将为其构建的所有程序。 因此,让我们开始测试词法分析器:我们只需要验证词法分析器产生的标记序列就是我们所关注的。

 package me.tomassetti.sandy  import me.tomassetti.langsandbox.SandyLexer  import org.antlr.v4.runtime.CharStreams  import java.util.*  import kotlin.test.assertEquals  import org.junit.Test as test  SandyLexerTest { class SandyLexerTest { 
     fun lexerForCode(code: String) = SandyLexer(CharStreams.fromString(code)) 
     fun lexerForResource(resourceName: String) = SandyLexer(ANTLRInputStream( this .javaClass.getResourceAsStream( " https://mk0tuzolorusfnc7thxk.kinstacdn.com/ ${resourceName}.sandy" ))) 
     fun tokens(lexer: SandyLexer): List<String> { 
         val tokens = LinkedList<String>() 
         do { 
            val t = lexer.nextToken() 
             when (t.type) { 
                 - 1 -> tokens.add( "EOF" ) 
                 else -> if (t.type != SandyLexer.WS) tokens.add(lexer.ruleNames[t.type - 1 ]) 
             } 
         } while (t.type != - 1 ) 
         return tokens 
     } 
     @test fun parseVarDeclarationAssignedAnIntegerLiteral() { 
         assertEquals(listOf( "VAR" , "ID" , "ASSIGN" , "INTLIT" , "EOF" ), 
                 tokens(lexerForCode( "var a = 1" ))) 
     } 
     @test fun parseVarDeclarationAssignedADecimalLiteral() { 
         assertEquals(listOf( "VAR" , "ID" , "ASSIGN" , "DECLIT" , "EOF" ), 
                 tokens(lexerForCode( "var a = 1.23" ))) 
     } 
     @test fun parseVarDeclarationAssignedASum() { 
         assertEquals(listOf( "VAR" , "ID" , "ASSIGN" , "INTLIT" , "PLUS" , "INTLIT" , "EOF" ), 
                 tokens(lexerForCode( "var a = 1 + 2" ))) 
     } 
     @test fun parseMathematicalExpression() { 
         assertEquals(listOf( "INTLIT" , "PLUS" , "ID" , "ASTERISK" , "INTLIT" , "DIVISION" , "INTLIT" , "MINUS" , "INTLIT" , "EOF" ), 
                 tokens(lexerForCode( "1 + a * 3 / 4 - 5" tokens(lexerForCode( "1 + a * 3 / 4 - 5" ))) 
     } 
     @test fun parseMathematicalExpressionWithParenthesis() { 
         assertEquals(listOf( "INTLIT" , "PLUS" , "LPAREN" , "ID" , "ASTERISK" , "INTLIT" , "RPAREN" , "MINUS" , "DECLIT" , "EOF" ), 
                 tokens(lexerForCode( "1 + (a * 3) - 5.12" tokens(lexerForCode( "1 + (a * 3) - 5.12" ))) 
     }  } 

结论和下一步

我们从第一步开始:建立项目并构建词法分析器。

使这种语言在实践中可用之前,我们还有很长的路要走,但是我们开始了。 接下来,我们将使用相同的方法来处理解析器:构建一些简单的东西,以便我们可以通过命令行进行测试和编译。

翻译自: https://www.javacodegeeks.com/2020/06/getting-started-with-antlr-building-a-simple-expression-language.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值