文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

这篇文章是本系列的第一篇。其目的就是讲述如何创建一个有用的语言以及介绍相关支持的工具。

我们使用一个非常简单的表达式语言作为开始。我们将在自己的语言沙箱中构建它,姑且把这个语言叫做 'Sandy.'

我想对一个语言来说工具是至关重要的,所以尽管是非常简单的语言我们也用了很多丰富的工具。开发一门语言需要解析器、解释器、编译器以及编辑器等等。在我看来虽然有很多素材用来构建简单解析器,但却很少有分享的内容介绍如何让一门语言变得实用起来。

第 1 段(可获 1.71 积分)

我只想把注意力放在这个方面 —— 开发一个简单但完全可用的语言。通过它来提升你的语言能力。

本文涉及的代码存放在 GitHub 上。本文所涉及的代码对应 01_lexer 标签。

语言本身

这个语言允许定义变量和表达式,它将支持:

  • 整数和十进制数值
  • 变量定义和赋值
  • 基本的算术运算(加减乘除)
  • 括号的使用

语言简单示例:

vara=10/3
varb=(5+3)*2
varc=a/b
第 2 段(可获 1.05 积分)

我们所用的工具

我们将使用如下工具:

  • 使用 ANTLR 来生成词法分析器和解析器
  • 使用 Gradle 作为构建工具
  • 在 Kotlin 中编写代码,这个非常基础,我也是刚开始学

项目设置

我们的 build.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: '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"
  testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
  testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
  testCompile 'junit:junit:4.12'
}

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")
    }
}
第 3 段(可获 0.63 积分)

接下来这么运行:

  • ./gradlew idea 命令用来生成 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                 : [_]*[a-z][A-Za-z0-9_]* ;
第 4 段(可获 0.49 积分)

现在我们可以通过简单运行 ./gradlew generateGrammarSource 来生成前面定义的语法对应的词法分析器。

测试词法分析器

在构建一门新的语言时,测试肯定是非常重要的,而且也非常关键:如果这个工具无法正确支持你的语言,这将会影响到所有语言编写的程序。因此开始测试吧,我们只是验证词法分析器生成的符号序列是否如我们所期待的那样。

package me.tomassetti.sandy

import me.tomassetti.langsandbox.SandyLexer
import org.antlr.v4.runtime.ANTLRInputStream
import java.io.*
import java.util.*
import org.junit.Test as test
import kotlin.test.*

class SandyLexerTest {

    fun lexerForCode(code: String) = SandyLexer(ANTLRInputStream(StringReader(code)))

    fun lexerForResource(resourceName: String) = SandyLexer(ANTLRInputStream(this.javaClass.getResourceAsStream("/${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")))
    }

    @test fun parseMathematicalExpressionWithParenthesis() {
        assertEquals(listOf("INTLIT", "PLUS", "LPAREN", "ID", "ASTERISK", "INTLIT", "RPAREN", "MINUS", "DECLIT", "EOF"),
                tokens(lexerForCode("1 + (a * 3) - 5.12")))
    }
}
第 5 段(可获 0.98 积分)

结论以及下一步

这是开始的一小步,我们只是对项目进行设置然后构建词法分析器。

制作一门实用的语言前面还有很长的路,但我们开始了。接下来我们主要是实用相同的方法来构建一个词法解析器。实用命令行构建一些简单的程序并进行测试。

下一步: 实用 ANTLR 和 Kotlin 构建和测试词法解析器

第 6 段(可获 0.78 积分)

文章评论