词法分析 | 青训营笔记

65 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 17 天

2.1 前言

上期笔记我们已经铺叙了有关编译逻辑层面的四个阶段(详情见《一切从编译开始》),本期笔记我们继续探讨关于 Go 语言的词法分析和语法分析。

2.2 词法分析

2.2.1 关于 Token

Token中文翻译为标记,是一个字符串,也是构成源代码的最小单位,从输入字符流中生成标记的过程叫作标记化(tokenization),在这个过程中,词法分析器还会对标记进行分类。编译器会从左到右扫描我们的源代码,将其中的字符流分割成一个一个的Token

2.2.2 Go 词法分析的实现

Go 语言的词法分析是通过cmd/compile/internal/syntax.scanner结构体实现的,这个结构体会持有当前扫描的数据源文件、启用的模式和当前被扫描到的Token

type scanner struct {
	source
	mode   uint
	nlsemi bool

	// current token, valid after calling next()
	line, col uint
	blank     bool // line is blank up to col
	tok       token
	lit       string   // valid if tok is _Name, _Literal, or _Semi ("semicolon", "newline", or "EOF"); may be malformed if bad is true
	bad       bool     // valid if tok is _Literal, true if a syntax error occurred, lit may be malformed
	kind      LitKind  // valid if tok is _Literal
	op        Operator // valid if tok is _Operator, _AssignOp, or _IncOp
	prec      int      // valid if tok is _Operator, _AssignOp, or _IncOp
}

Go 语言中支持的全部Token类型都在src/cmd/compile/internal/syntax/tokens.go文件中定义了,所有的Token类型都是正整数,常见Token定义如:操作符、括号和关键字等。

const (
	_    token = iota
	_EOF       // EOF

	// operators and operations
	_Operator // op
	...

	// delimiters
	_Lparen    // (
	_Lbrack    // [
	...

	// keywords
	_Break       // break
	...
	_Type        // type
	_Var         // var

	tokenCount //
)

Go 语言中定义的Token将语言中的元素分为了四个类型,分别是名称和字面量、操作符、分隔符、和关键字,其词法分析主要由 cmd/compile/internal/syntax.scanner结构体中的cmd/compile/internal/syntax.scanner.next方法驱动,而驱动主体是一个switch/case结构,在for循环中不断获取最新字符,并通过cmd/compile/internal/syntax.source.nextch方法追加到 cmd/compile/internal/syntax.scanner持有的缓冲区中。

func (s *scanner) next() {
	...
	s.stop()
	startLine, startCol := s.pos()
	for s.ch == ' ' || s.ch == '\t' || s.ch == '\n' && !nlsemi || s.ch == '\r' {
		s.nextch()
	}

	s.line, s.col = s.pos()
	s.blank = s.line > startLine || startCol == colbase
	s.start()
	if isLetter(s.ch) || s.ch >= utf8.RuneSelf && s.atIdentChar(true) {
		s.nextch()
		s.ident()
		return
	}

	switch s.ch {
	case -1:
		s.tok = _EOF

	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
                s.number(false)  //整数匹配
	...
	}
}

值得一提的是,早期 Go 语言其实也用过 lex 来生成词法解析器,但后来还是使用 Go 来实现了词法分析器,用自己写的词法分析器来解析自己。

词法分析到此告一段落,过度深入了解无异于囫囵吞枣,语法分析将在下期笔记中探究。

文章参考:左书祺《Go语言设计与编译》——「编译原理」