尝试用GO写python编译器:创建互动式命令号窗口REPL

6 篇文章 0 订阅
5 篇文章 1 订阅

上一节完成的词法解析器存在一些明显问题,例如在识别数字时,面对字符串“123abc",它会识别为两者情况,分别为NUMBER:123,然后是IDENTIFIER:anc,实际上这样的字符串应该被认为是语法错误。另外我们还有一些”连号“操作符没有处理,例如“==, !=, – ,++"等。

本节我们将在上一节的基础上对词法解析器做进一步完善。首先在token.go里面添加新的操作符定义:
···
const (
ILLEGAL = “ILLEGAL”
EOF = “EOF”
IDENTIFIER = “IDENTIFIER” //变量类型对应的归类
NUMBER = “NUMBER” //数值类型对应的归类
ASSIGN = “=” //赋值操作符
PLUS = “+” //加号操作符
LPAR = “(”
RPAR = “)”
LBRACE = “{”
RBRACE = “}”
COMMA = “,”
COLON = “:”
DEF = “def” //关键字
INT = “int”
RETURN = “return”
ASSERT = “assert”
AND = “and”

//第三节添加
TRUE = "True"
FALSE = "False"
IF = "if"
ELSE = "else"
EQUAL = "=="
NOTEQUAL = "!="
GREATEREQUAL = ">="
LESSEQUAL = "<="
MINUS = "-"
BANG = "!"
ASTERISK = "*"
SLASH = "/"
LT = "<"
GT = ">"
//第三节添加

)

var keywords = map[string]TokenType {
“def” : DEF,
“int” : INT,
“return” : RETURN,
“assert” : ASSERT,
“and” : AND,
//第三节添加
“if” : IF ,
“else” : ELSE,
“True” : TRUE,
“False” : FALSE,
//第三节添加
}
···
现在有关问题在于,有些操作符必须读取双字符才能认定,在lexer.go中的NextToken函数,它的switch只能接收单个字符,因此要识别">=", "!="等这些符号时,我们需要在读取到第一个字符时,还需要再读取下一个字符,这样获取到两个字符后才能做出判断,于是我们在lexer.go里面再添加一个函数:

func (l *Lexer) peekChar() byte {
	if l.readPosition >= len(l.input) {
		return 0 
	} else {
		return l.input[l.readPosition]
	}
}

当解析器读取到符号"=", “!”, “<”, ">"时,它还需要借助上面的函数获取下一个字符,这样才能决定当前遇到的操作符是哪一种,代码如下:

func (l *Lexer) NextToken() token.Token{//读取一个字符,判断是否属于特定分类
    var tok token.Token
	//忽略空格,回车,换行等特定字符
	l.skipSpecialChar()

	switch l.ch {
	 。。。。
	 //第三节添加
	case '-':
		tok = newToken(token.MINUS, l.ch)
	case '*':
		tok = newToken(token.ASTERISK, l.ch)
	case '/':
		tok = newToken(token.SLASH, l.ch)
	case '=' :
		if l.peekChar() == '=' {
			//遇到==操作符
			ch := l.ch 
			l.readChar()
			tok = token.Token{Type: token.EQUAL, Literal: string(ch) + string(l.ch}
		} else {
			tok = newToken(token.ASSIGN, l.ch)
		}
	case '!':
		if l.peekChar() == '=' {
			//操作符!=
			ch := l.ch 
			l.readChar()
			tok = token.Token{Type: token.NOEQUAL, Literal: string(ch) + string(l.ch)}
		} else {
			tok = newToken(token.BANG, l.ch)
		}
	case '<':
		if l.peekChar() == '=' {
			//操作符!=
			ch := l.ch 
			l.readChar()
			tok = token.Token{Type: token.LESSEQUAL, Literal: string(ch) + string(l.ch)}
		} else {
			tok = newToken(token.LT, l.ch)
		}
	case '>':
		if l.peekChar() == '=' {
			//操作符!=
			ch := l.ch 
			l.readChar()
			tok = token.Token{Type: token.GREATEREQUAL, Literal: string(ch) + string(l.ch)}
		} else {
			tok = newToken(token.GT, l.ch)
		}
     。。。。
     }
     。。。。     
}

现在我们可以再次完善用于测试的python代码,在lexer_test.go里面进行修改如下:


func TestNextToken2(t *testing.T) {
	input := `
	def add(x, y):
	    assert 0 <= x <= y
	    z = x + y
	    return z
	`

	tests := []struct {
		expectedType token.TokenType 
		expectedLiteral string 
	} {
		{token.DEF, "def"},
		{token.IDENTIFIER, "add"},
		{token.LPAR, "("},
		{token.IDENTIFIER, "x"},
		{token.COMMA, ","},
		{token.IDENTIFIER, "y"},
		{token.RPAR, ")"},
		{token.COLON, ":"},
		//第三节添加
		{token.ASSERT, "assert"},
		{token.NUMBER, "0"},
		{token.LESSEQUAL, "<="},
		{token.IDENTIFIER, "x"},
		{token.LESSEQUAL, "<="},
		{token.IDENTIFIER, "y"},
		//第三节添加
		{token.IDENTIFIER, "z"},
		{token.ASSIGN, "="},
		{token.IDENTIFIER, "x"},
		{token.PLUS, "+"},
		{token.IDENTIFIER, "z"},
		{token.RETURN, "return"},
		{token.IDENTIFIER, "z"},
	}

	l := New(input)
	for i, tt := range tests {
		tok := l.NextToken()

		if tok.Type != tt.expectedType {
			t.Fatalf("test[%d] - tokenType wrong. expected=%q, got=%q",
		    i, tt.expectedType, tok.Type)

			if tok.Literal != tt.expectedLiteral {
				t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q",
			    i, tt.expectedLiteral, tok.Literal)
			}
		}
	}
}

执行go test后,可以发现上面用例可以通过,这意味着我们的词法解析请求已经能够识别比较复杂的python代码了。

有过Python开发经验的同学都知道,在命令号行窗口输入命令python后,我们可以进入一个互动环境,在里面可以直接输入代码,点击回车就能直接运行,现在我们也来实现这个功能。在根目录创建文件夹repl,然后在里面创建文件repl.go,然后输入代码如下:

package repl 

import (
	"bufio"
	"fmt"
	"io"
	"lexer"
	"token"
)

const PROMPT = ">>"

func Start(in io.Reader, out io.Writer) {
	scanner := bufio.NewScanner(in) //从控制台获取输入

	for {
		fmt.Printf(PROMPT)
		scanned := scanner.Scan() //点击回车后返回输入内容
		if !scanned {
			return //没有输入内容
		}

		lien := scanner.Text() //当前输入的内容
		l := lexer.New(line) 

		for tok := l.NextToken(); tok.Type != token.EOF; tok = l.NextToken() {
			fmt.Printf("%+v\n", tok) //输出解析的结果
		}
	}
}

接下来我们把main.go的内容输入如下:

package repl 

import (
	"bufio"
	"fmt"
	"io"
	"lexer"
	"token"
)

const PROMPT = ">>"

func Start(in io.Reader, out io.Writer) {
	scanner := bufio.NewScanner(in) //从控制台获取输入

	for {
		fmt.Printf(PROMPT)
		scanned := scanner.Scan() //点击回车后返回输入内容
		if !scanned {
			return //没有输入内容
		}

		line := scanner.Text() //当前输入的内容
		l := lexer.New(line) 

		for tok := l.NextToken(); tok.Type != token.EOF; tok = l.NextToken() {
			fmt.Printf("%+v\n", tok) //输出解析的结果
		}
	}
}

上面代码运行后结果如下:
请添加图片描述
可以看到,我们当前完成的工作还真有一点Python编译环境的味道。完整代码请点击这里{https://github.com/wycl16514/-GO-python-REPL.git}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值