

从词法分析角度看 Go 代码的组成 - 码途漫漫 - SegmentFault 思否
source link: https://segmentfault.com/a/1190000020894108
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

从词法分析角度看 Go 代码的组成
之前的 Go 笔记系列,已经完成到了开发环境搭建,原本接下来的计划就是到语法部分了,但后来一直没有前进。主要是因为当时的工作比较忙,分散了精力,于是就暂时放下了。
最近,准备重新把之前计划捡起来。
第一步,肯定是了解 Go 基础语法部分。原本计划是写 Go 编码的一些基础知识,但纯粹聊什么是关键字、标识符、字面量、操作符实在有点无聊。
突然想到,词法分析这块知识还没仔细研究过,那就从这个角度出发吧。通过逐步地拆解,将各个 token 进行归类。
我们知道,编译型语言(比如 Go)的源码要经过编译和链接才能转化为计算机可以执行的程序,这个过程的第一步就是词法分析。
什么是词法分析呢?
它就是将源代码转化为一个个预先定义的 token 的过程。为了便于理解,我们将其分为两个阶段进行介绍。
第一阶段,对源码串进行扫描,按预先定义的 token 规则进行匹配并切分为一个个有语法含义、最小单元的字符串,即词素(lexme),并在此基础上将其划归为某一类 token。这个阶段,一些字符可能会被过滤掉,比如,空白符、注释等。
第二阶段,通过评估器 Evaluator 评估扫描出来的词素,并确定它字面值,生成最终的 Token。
是不是有点不好理解呢?
如果之前从未接触过这块内容,可能没有直观感受。其实,看着很复杂,但的确非常简单。
一个简单的示例
先看一段代码,经典的 hello world,如下:
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
我们可以通过这个例子的源码逐步拆解词法分析的整个流程。
什么是词素
理论性的概念就不说了,直接看效果吧。
首先,将这段示例代码通过词法分析的第一阶段,我们将会得到如下内容:
package
main
\n
import
"fmt"
\n
func
main
(
)
{
\n
fmt
.
Println
(
"Hello World"
)
\n
}
输出的这一个个独立的字符序列就是词素。
词素的切分规划和语言的语法规则有关。此处的输出中除了一些可见的字符,换行符同样也具有语法含义,因为 Go 不像 C/C++ 必须是分号分隔语句,也可以通过换行符分隔。
源码分割为一个个词素的过程是有一定的规则的,这和具体的语言有关。但虽有差异,其实规则都差不多,无非两种,一是通过无语法含义的字符(空格符、制表符等)切分,还有是每个词素可以用作为分隔符。
什么是 token
token,也称为词法单元、记号等,它由名称和字面值两部分组成。从词素到 token 有固定的对应关系,而且并非所有的 token 都有字面值。
将 hello world 的源码转化为 token,我们将会得到如下的一张对应表格。
lexme | name | value |
---|---|---|
package | PACKAGE | "package" |
main | IDENT | "main" |
n | SEMICOLON | "n" |
import | IMPORT | "import" |
"fmt" | STRING | "\"fmt\"" |
n | SEMICOLON | "n" |
func | FUNC | "func" |
main | IDENT | "main" |
( | LPAREN | "" |
) | RPAREN | "" |
{ | LBRACE | "" |
fmt | IDENT | "fmt" |
. | PERIOD | "" |
Println | IDENT | "Println" |
( | LPAREN | "" |
"Hello World" | STRING | ""Hello World"" |
) | RPAREN | "" |
n | SEMICOLON | "n" |
} | LBRACE | "" |
n | SEMICOLON | "n" |
稍微有点长,因为这里没有省略。表格中的第一列是原始内容,第二列对应的 token 的名称,最后一列是 token 的字面值。
从表格中可以观察出,其中有一些 token 并没有值,比如,括号、点,名称本身已经表示了它们的内容。
token 的分类
token 一般可以分为关键字、标识符、字面量、操作符这四个大类。这个分类其实在 Go 的源码中有非常明显的体现。
查看源码文件 src/go/token/token.go,将会找到 Token
类型如下的几个方法。
// 是否是字面常量
func (tok Token) IsLiteral() bool { return literal_beg < tok && tok < literal_end }
// 是否是操作符
func (tok Token) IsOperator() bool { return operator_beg < tok && tok < operator_end }
// 是否是关键字
func (tok Token) IsKeyword() bool { return keyword_beg < tok && tok < keyword_end }
代码非常简单,通过比较确定 Token
是否位于指定范围确定它的类型。上面的这三个方法分别对应于判断 Token
是字面常量、操作符还是关键字。
额?怎么没有标识符呢?
当然也有啦,只不过它不是 Token
的方法,而是单独的一个函数。如下:
func IsIdentifier(name string) bool {
for i, c := range name {
if !unicode.IsLetter(c) && c != '_' && (i == 0 || !unicode.IsDigit(c)) {
return false
}
}
return name != "" && !IsKeyword(name)
}
我们常说的变量、常量、函数、方法的名称不能为关键字,且必须是由字母、下划线或数字组成,且名称的开头不能为数字的规则,看到这个函数是不是一些就明白了。
到这里,其实已经写的差不多了。但想想还是拿其中一个类型再简单说说吧。
就以关键字为例吧,Go 中的关键字有哪些呢?
继续看源码。将之前那段如何判断一个 token
是关键字的代码再看一遍。如下:
func (tok Token) IsKeyword() bool {
return keyword_beg < tok && tok < keyword_end
}
只要 Token
大于 keyword_beg
且小于 keyword_end
即为关键字,看起来还挺好理解的。那在 keyword_beg
和 keyword_end
之间有哪些关键字呢?代码如下:
const (
...
keyword_beg
// Keywords
BREAK
CASE
CHAN
CONST
CONTINUE
...
SELECT
STRUCT
SWITCH
TYPE
VAR
keyword_end
...
)
总共梳理出了 25 个关键字。如下:
break case chan const continue
default defer else fallthrough for
func go goto if import
interface map package range return
select struct switch type var
关键字的确挺少的。可见。。。
是不是猜到我要说,Go 语言就是简洁,关键字的都这么少。你看 Java,足足有 53 个关键字,其中有两个是保留字。你再看看 Go,连保留字都没有,就是这么自信。
既然你猜到了,那我还是先不说了吧。
操作符和字面常量就不追了,思路都是一样的。
Go 中的操作符有 47 个,比如赋值运算符、位运算符、算术运算符,比较运算符,还有其他的操作符。相信我吧,都是从源码中数出来的,没有看任何资料。[此处应该放个捂脸笑]。
字面常量呢?
有 5 种类型,分别是 INT(整型)、FLOAT(浮点型)、IMG(复数类型)、CHAR(字符型)、STRING(字符串型)。
文章写完了,前面扯了那么一堆废话,其实就只是为了介绍 Go 语法中用到的关键字、标识符、运算符、字面量从哪里找。并且,最终它们如何使用也没有怎么说明。
纯粹为了好玩吗?当然不是(是)。因为。。。,先不剧透了,避免后面尴尬。
Recommend
-
57
文章目录 前言 JS的正则表达式有两种声明方法,一种是构造函数 var reg = new RegExp(pattern, flag); ,另一种是字面量 var reg = / pattern / flag ,其中...
-
36
一直对词法分析与解析的话题比较感兴趣,最近发现了好几篇相关的优秀文章,准备好好翻译和研究下。我的理解,词法分析与解析的应用还是比较广泛的,无论简单的配置文件、各种模板语言、还是我们每天在写编程语言都离不开它。 本篇...
-
31
本文是关于词法器实现的具体介绍,如果在阅读时遇到困难,建议参考源码阅读,文中的代码片段为了介绍思路。如何解析会在下一篇介绍。 最近简单看了下 Go 源码,在 src/go 目录下有几个模块,token、scanner 和 parser 应该就是 Go...
-
26
最近发现我的翻译是越来越随性了,刚开始文章翻译的时候比较拘束,现在更多强调可读性,比如有些对文章大意没有什么影响的文字我现在都会选择直接跳过。 这篇文章主要是关于 INI 解释器的 parser 实现,它会从上一节中 Lexer 中接...
-
42
之前的 Go 笔记系列,已经完成到了开发环境搭建,原本接下来的计划就是到语法部分了,但后来一直没有前进。主要是因为当时的工作比较忙,分散了精力,于是就暂时放下了。 最近,准备重新把之前计划捡起来。 第一步,肯定是...
-
9
C语言编译器...
-
8
ucc编译器(词法分析)
-
10
连载《Chrome V8 原理讲解》第四篇 V8词法分析源码讲解,Token字生成灰豆chrome v8连载,3~4天一篇,持续更新中...
-
3
Go编译原理系列3(词法分析)在上一篇文章中,介绍了词法分析中的核心技术,有穷自动机(DFA),以及两个常见的词法分析器的使用及工作原理。在这个基础上去看Go的词法分析源码会轻松许多
-
5
本科阶段一直没学过编译原理,因此找到一门UCB的课程CS164——Programming Language&Compilers来学习一下编译原理的基本内容。第一节主要讲的是整个课程的Introduction和词法分析。 Introduction 我们平时常用...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK