使用 Go 构建一个解释型语言(2)

我们在后面将着眼于更多的一些符号. 上面的示例定义了一个“生产规则”. 一个生产规则可能包含两个词汇元素: 非终端和终端. 终端是不能使用语法规则不能被改变的文字. 非终端则是可以被替换的符号, 可以把它看做是一个占位符或者一个变量. 它们有时会被称为“语法变量”. 在上面的示例中, 标识符,字母和数字都是非终端符号. 而 "Z", "0", "1", 都是终端符号的例子,它们是常量字符,也就是说它们不能被改变.

现在来看,上面的语法中所有的符号都意味着什么呢? 一个字母的定义是:

letter = "A" | "a" | ... "Z" | "z" | "_";

为了能够理解,要像读英语一样阅读它,例如,上面的语法被读作 “A” 或者 “a” 到 “Z” 或者 “z” 或者 “_”. 因此一个字母可以是任何从 a-Z 或者一个下划线的东西.

我们如此定义一个数字:

digit = { "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" };

这意味着一个数字可以是 “0” 或者 “1” 或者 “2” … 你理解到点子上了. 不过, 要注意这里的大括号. 如果你还记得我们在上面提供的列表, 括弧代表了重复, 指定重复 0 到 n 次, 这里的n可以是任何一个数字. 这就意味着一个数字可以是 0 - 9 总共重复 n 多次, 因此 123 是对的, 5123 也是对的.

最后是标识符:

identifier = letter { letter | digit };

目前我们理解了字母(letter)和数字(digit)的意思, 我们现在就能够理解这个小的生产规则. 基本上,一个标识符必须以一个字符开头,其后可以是0个或者更多个不同字母或者数字的重复. 例如,a_, a_a, a_1, a__, 等等都是正确的标识符.

这两个阶段的词法和语法解析通常指的是作为前端的编译器和解释器。现在,让我们开始写一些代码,我将使用GO来编写。所有的源代码将公布在我的 Github页面上。如果你接着使用GO来编写,首先为你的项目建立一个新的目录并且设置好你的main go文件。刚好,我编写了一个简单的hello world文件来进行测试。GO拥有一个神奇的工作空间系统,因此一开始,你就需要创建你的工作空间,我一直使用Linux来作为我的工作空间,因此我使用GO设置$HOME/go 的环境变量
。为方便起见,GO推荐我们增加这个设置到达我们的路径:

mkdir $HOME/go  export PATH=$PATH:$GOPATH/bin

我的项目的基本路径是在 github.com/felixangell。

你可以找到你想要的,或者你的 github 用户名:

mkdir -p $GOPATH/src/github.com/yourusername

现在开始设置我们的解释器程序,我们在个人目录下创建一个文件夹,名字可以是你给这个解释器起的任何名字,我叫它 vident。我们进入这个目录。

mkdir $GOPATH/src/github.com/felixangell/vident
cd $GOPATH/src/github.com/felixangell/vident

然后我们创建一个简单的文件作为测试用,可以直接拷贝这一部分:

package main
 
import "fmt"
 
func main() {
  fmt.Printf("hello, world\n");
}

把他保存到我们刚刚建立的文件夹 vident 中,名字为 main.go。现在我们编译并运行它:

go install
vident

因为我们正在用工程目录结构系统,我们需要添加 bin 目录到我们的目录,然后简单的运行上面的代码。当你运行时,你应该可以看到输出了“hello, world”。

那么接下来我们要定义我们的语言。Vident 是一门简单的语言,我们从一些小的特性入手,然后我们再转移到复杂的示例。下面是 Vident 的一个代码实例:

let x = 5 + 5 print: x, "hello", x

我需要把->改为:,否则熟悉 Tumblr 格式的人对它很多抱怨,抱歉!我们语言的 EBNF 语法:

letter = "A" | "a" | ... "Z" | "z" | "_"; digit = { "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" }; identifier = letter { letter | digit };  number_literal = digit | [ "." digit ]; string_literal = """ letter { letter } """; char_literal = "'" letter "'"; literal = number_literal | string_literal | char_literal; binaryOp = "+" | "-" | "/" | "*"; binary_expr = expression binaryOp expression; expression = binary_expr | function_call | identifier | literal; let_stat = "let" identifier [ "=" expression ]; arguments = { expression "," }; function_call = identifier [ ":" arguments ]; statement = let_stat | function_call;

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/5bb731f892cdd9493dc8d24b61c08050.html