这篇文章是系列文章的一部分。 本系列的目的是描述如何创建有用的语言和所有支持工具。
写完这一系列文章后,我改进了方法,进行了扩展,并澄清为《 如何创建实用的轻量级语言》一书。
在本文中,我们将开始研究一种非常简单的表达语言。 我们将在语言沙箱中构建它,因此我们将其称为语言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" )))
} }
结论和下一步
我们从第一步开始:建立项目并构建词法分析器。
使这种语言在实践中可用之前,我们还有很长的路要走,但是我们开始了。 接下来,我们将使用相同的方法来处理解析器:构建一些简单的东西,以便我们可以通过命令行进行测试和编译。