一直以来对编译器/解释器等都较有兴趣。我非科班出身,当初还在大学时,只是马马虎虎看完了《编译原理》之类教材,上机非常少,对龙书之类圣经也只是浅尝辄止而已。工作至今,基本已将编译原理相关知识忘记得差不多了,可能也就还对譬如预处理词法分析语法分析 AST 生成等基础性的概念还有点印象罢。

  约 1 年多前,我也有想法搞一套基于简化的 Pascal 语法的带类型的脚本语言“编译器”(PaxCompiler 之类可能太复杂了),并将此脚本语言编写的脚本与 Golang 交互起来。当然这只是我个人的业余兴趣而已,至于是否会付诸行动、能搞成怎样都是未知。而选择 Pascal 作为参考改编语言的原因,其一我比较喜欢它的语言设计,其二它曾是我某段时间内的工作语言所以感情成分使然,其三较之诸如 Python、Lua 我更喜欢带类型的脚本语言(TypeScript?我不太喜欢 JavaScript 的语法...),当然,Pascal 的语法形式也确实比较方便为之开发编译器/解释器。

  而短期内,个人恐怕没有太多精力去啃龙书之类,于是索性,看点基础资料且按此系列教程之类慢慢温习并从 tokenizer 开始一步步实现自己的 EcoPascal——即便最终,它只是个玩具脚本语言而已。

  近 2 天趁有空,粗略看了前文所述教程的前两章,并以 Golang 重写了这两章里的解释程序(代码写得有些粗放)。

  第一章:

package interpreter

import (
"fmt"
"unicode"
) // Token types
//
// EOF (end-of-file) token is used to indicate that
// there is no more input left for lexical analysis
type TokenType int const (
cTokenTypeOfNone TokenType = iota
cTokenTypeOfInteger
cTokenTypeOfPlusSign
cTokenTypeOfEOF
) type token struct {
t TokenType // token type: INTEGER, PLUS, or EOF
v interface{} // token value: 0, 1, 2. 3, 4, 5, 6, 7, 8, 9, '+', or None
} func newToken(t TokenType, v interface{}) token {
return token{
t: t,
v: v,
}
} type Interpreter struct {
text []rune // client string input, e.g. "3+5"
pos int // an index into text
currToken token // current token instance
} func New() *Interpreter {
return &Interpreter{
text: []rune(""),
pos: 0,
currToken: newToken(cTokenTypeOfNone, nil),
}
} func convToDigit(c rune) (int, bool) {
if unicode.IsDigit(c) {
return int(c - '0'), true
}
return 0, false
} // Lexical analyzer (also known as scanner or tokenizer)
//
// This method is responsible for breaking a sentence apart into tokens.
// One token at a time.
func (self *Interpreter) getNextToken() token {
text := self.text // is self.pos index past the end of the self.text ?
// if so, then return EOF token because there is no more
// input left to convert into tokens
if self.pos > len(text)-1 {
return newToken(cTokenTypeOfEOF, nil)
} // get a character at the position self.pos and decide
// what token to create based on the single character
// var currChar interface{} = text[self.pos]
currChar := text[self.pos] // if the character is a digit then convert it to
// integer, create an INTEGER token, increment self.pos
// index to point to the next character after the digit,
// and return the INTEGER token
if v, ok := convToDigit(text[self.pos]); ok {
self.pos += 1
return newToken(cTokenTypeOfInteger, v)
} if currChar == '+' {
self.pos += 1
return newToken(cTokenTypeOfPlusSign, '+')
} panic(fmt.Sprintf("Error parsing input: %s", string(self.text)))
} // compare the current token type with the passed token type
// and if they match then "eat" the current token
// and assign the next token to the self.currToken,
// otherwise raise an exception.
func (self *Interpreter) eat(tokenType TokenType) {
if self.currToken.t == tokenType {
self.currToken = self.getNextToken()
return
} panic(fmt.Sprintf("Error parsing input: %s", self.text))
} // parse "INTEGER PLUS INTEGER"
func (self *Interpreter) Parse(s string) int {
self.text = []rune(s)
self.pos = 0 // set current token to the first token taken from the input
self.currToken = self.getNextToken() // we expect the current token to be a single-digit integer
left := self.currToken
self.eat(cTokenTypeOfInteger) // we expect the current token to be a '+' token
// op := self.currToken
self.eat(cTokenTypeOfPlusSign) // we expect the current token to be a single-digit integer
right := self.currToken
self.eat(cTokenTypeOfInteger) // after the above call the self.current_token is set to EOF token.
// at this point INTEGER PLUS INTEGER sequence of tokens
// has been successfully found and the method can just
// return the result of adding two integers, thus
// effectively interpreting client input
return left.v.(int) + right.v.(int)
}

  第二章:

package interpreter

import (
"fmt"
"unicode" "github.com/ecofast/rtl/sysutils"
) // Token types
//
// EOF (end-of-file) token is used to indicate that
// there is no more input left for lexical analysis
type TokenType int const (
cTokenTypeOfNone TokenType = iota
cTokenTypeOfInteger
cTokenTypeOfPlusSign
cTokenTypeOfMinusSign
cTokenTypeOfEOF
) type token struct {
t TokenType // token type: INTEGER, PLUS, MINUS, or EOF
v interface{} // token value: non-negative integer value, '+', '-', or None
} func newToken(t TokenType, v interface{}) token {
return token{
t: t,
v: v,
}
} type Interpreter struct {
text []rune // client string input, e.g. "3 + 5", "12 - 5", etc
pos int // an index into text
currToken token // current token instance
currChar rune
} func New() *Interpreter {
return &Interpreter{
text: []rune(""),
pos: 0,
currToken: newToken(cTokenTypeOfNone, nil),
currChar: 0,
}
} // Advance the 'pos' pointer and set the 'currChar' variable
func (self *Interpreter) advance() {
self.pos += 1
if self.pos > len(self.text)-1 {
self.currChar = 0
} else {
self.currChar = self.text[self.pos]
}
} func (self *Interpreter) skipWhiteSpace() {
for self.currChar != 0 && unicode.IsSpace(self.currChar) {
self.advance()
}
} // Return a (multidigit) integer consumed from the input
func (self *Interpreter) integer() int {
ret := ""
for self.currChar != 0 && unicode.IsDigit(self.currChar) {
ret += string(self.currChar)
self.advance()
}
return sysutils.StrToInt(ret)
} // Lexical analyzer (also known as scanner or tokenizer)
//
// This method is responsible for breaking a sentence apart into tokens.
func (self *Interpreter) getNextToken() token {
for self.currChar != 0 {
if unicode.IsSpace(self.currChar) {
self.skipWhiteSpace()
continue
} if unicode.IsDigit(self.currChar) {
return newToken(cTokenTypeOfInteger, self.integer())
} if self.currChar == '+' {
self.advance()
return newToken(cTokenTypeOfPlusSign, '+')
} if self.currChar == '-' {
self.advance()
return newToken(cTokenTypeOfMinusSign, '-')
} panic(fmt.Sprintf("Error parsing input: %s", string(self.text)))
}
return newToken(cTokenTypeOfEOF, nil)
} // compare the current token type with the passed token type
// and if they match then "eat" the current token
// and assign the next token to the self.currToken,
// otherwise raise an exception.
func (self *Interpreter) eat(tokenType TokenType) {
if self.currToken.t == tokenType {
self.currToken = self.getNextToken()
return
} panic(fmt.Sprintf("Error parsing input: %s", self.text))
} // parse "INTEGER PLUS INTEGER" or "INTEGER MINUS INTEGER"
func (self *Interpreter) Parse(s string) int {
self.text = []rune(s)
self.pos = 0
self.currChar = self.text[self.pos] // set current token to the first token taken from the input
self.currToken = self.getNextToken() // we expect the current token to be an integer
left := self.currToken
self.eat(cTokenTypeOfInteger) // we expect the current token to be either a '+' or '-'
op := self.currToken
if op.t == cTokenTypeOfPlusSign {
self.eat(cTokenTypeOfPlusSign)
} else {
self.eat(cTokenTypeOfMinusSign)
} // we expect the current token to be an integer
right := self.currToken
self.eat(cTokenTypeOfInteger) // after the above call the self.current_token is set to EOF token.
// at this point either the INTEGER PLUS INTEGER or
// the INTEGER MINUS INTEGER sequence of tokens
// has been successfully found and the method can just
// return the result of adding or subtracting two integers, thus
// effectively interpreting client input
if op.t == cTokenTypeOfPlusSign {
return left.v.(int) + right.v.(int)
}
return left.v.(int) - right.v.(int)
}

  有了“核心”解释程序,使用起来就很简单了:

// part2 project main.go
package main import (
"bufio"
"fmt"
"os"
"part2/interpreter"
"strings"
) func main() {
fmt.Println("Let's Build A Simple Interpreter - Part 2") parser := interpreter.New()
reader := bufio.NewReader(os.Stdin)
for {
if s, err := reader.ReadString('\n'); err == nil {
fmt.Println(parser.Parse(strings.TrimSpace(s)))
continue
}
break
}
}

  

  本兴趣项目已托管至 Github,比较可能会不定期慢慢更新。

《Let's Build A Simple Interpreter》之 Golang 版的更多相关文章

  1. 简单物联网:外网访问内网路由器下树莓派Flask服务器

    最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...

  2. 利用ssh反向代理以及autossh实现从外网连接内网服务器

    前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...

  3. 外网访问内网Docker容器

    外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...

  4. 外网访问内网SpringBoot

    外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...

  5. 外网访问内网Elasticsearch WEB

    外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...

  6. 怎样从外网访问内网Rails

    外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...

  7. 怎样从外网访问内网Memcached数据库

    外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...

  8. 怎样从外网访问内网CouchDB数据库

    外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...

  9. 怎样从外网访问内网DB2数据库

    外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...

  10. 怎样从外网访问内网OpenLDAP数据库

    外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...

随机推荐

  1. nginx的headers_more模块的使用

    nginx的headers_more模块用于 添加.修改或清除 请求/响应头,该模块不是nginx自带的,默认不包含该模块,需要另外安装.幸运的是openresty默认包含了该模块,可以直接使用. 该 ...

  2. php ob缓存

    用PHP的ob_start();控制您的浏览器cache Output Control 函数可以让你自由控制脚本中数据的输出.它非常地有用,特别是对于:当你想在数据已经输出后,再输出文件头的情况.输出 ...

  3. [SharePoint2010开发入门经典]二、开始SPS2010开发

    本章概要: 1.了解SPS2010开发要素(包括工具,平台服务,开发选项) 2.熟悉主要开发工具和部署方案 3.安装.配置.简单开发案例 4.理解网站级别的安全设置

  4. python3连接Mairadb数据库

    <span style="font-size:18px;">#本代码演示的是python3.3.5下连接Mairadb数据库</span> <span ...

  5. Java向上转型和向下转型(附具体样例)

                                                Java向上转型和向下转型(附具体样例) 熬夜整理的关于Java向上和向下转型的样例,很的通俗易懂哦~~~~ 一 ...

  6. svn 服务器的搭建

     SVN服务器运行模式:模式1:svn服务器单独运行  监听: 3690端口    访问: svn://IP模式2: svn 服务器+ apache   : 80 端口  访问: http://IP  ...

  7. UVA 11825 Hackers’ Crackdown 状压DP枚举子集势

    Hackers’ Crackdown Miracle Corporations has a number of system services running in a distributed com ...

  8. HIT Software Construction Lab 5_经验总结

    前言: 终于写完lab5了,这次lab5是基于lab3的一次实验,主要是王忠杰老师提供了4个大约有50w行的大文件让我们根据自己所选应用读取其中两个并且创建轨道系统. 这次lab5优化的我很崩溃,因为 ...

  9. svn插件失效

    安装其他插件后,可能出现SVN插件失效了,在eclipse中完全找不到SVN的任何操作选项,此时可尝试通过以下办法解决: 把eclipse/configuration目录下的org.eclipse.u ...

  10. 17. Letter Combinations of a Phone Number[M]电话号码的字母组合

    题目 Given a string containing digits from 2-9 inclusive, return all possible letter combinations that ...