【Golang源码分析】解析执行命令complie(二)词法解析

前言:

当我们使用编程语言编写代码时,我们都知道代码都是写给人看的,只是除了人能看外编译器可以编译和执行。而这些复杂的代码其实是一堆堆字符串,只是我们训练编译器拆分解读。

而编译器将一堆堆代码的字符串分组的过程,我们把这个过程称之为词法解析。

本文中工具:

lex:3.1

golang : 1.13.4 darwin/amd64

dlv: 1.3.2

编译过程一般分为 6 步:扫描、语法分析、语义分析、源代码优化、代码生成、目标代码优化。

下图来自《程序员的自我修养》

词法解析相关知识:

说到词法解析我们可以关注两个工具:

Lex(LEXical compiler)和yacc(Yet Another Compiler Compiler),Lex 是一种生成扫描器的工具。扫描器是一种识别文本中的词汇模式的程序。yacc是语法分析器。

一个简单的例子demo.lex :

%%
[\t ]+  /* 忽略空白 */;
学而思轻课 |
轻课 |
好未来 |
zhaoyu |
学而思  {printf("%s : 棒棒哒\n",yytext);}
 
[a-zA-Z]+   {printf("%s: 这个字母\n",yytext);}
 
.|\n    {ECHO; /* 通常的默认状态 */}
%%
 
int main()
{
    yylex();
    return 0;
}
//必须要包含的函数
int yywrap()
{
    return 1;
}

生成lex文件,会默认生成一个lex.yy.c的源码文件。用lex的语法去编写,需要用lex生成对应的源码文件,才可以编译执行。

lex demo.lex

编译文件

gcc lex.yy.c -o demo

图中例子比较简单,输入(“学而思”、“轻课”、“zhaoyu”、“好未来”或者是“学而思轻课”)关键词则显示 %s:棒棒哒 ,如果输入字母则则显示 %s:这是一个字母。输入其他的就原样输出。其实都是一堆正则去做匹配,匹配到对应的关键词。PHP源码中也是这样的规则。

IBM关于LEX比较不错的文章。

https://www.ibm.com/developerworks/cn/linux/sdk/lex/

从上面的示例其实我们可以简单的理解为,词法解析。就是对我们关键词做拆分处理,然而在编译过程中。词法解析会将我们的代码拆分成一个个token。

Golang相关词法解析:

我们一起来看看go的词法解析过程;

default.go

package main
func demo1() {
   println("demo1")
}
func main() {
   demo1() 
   println("abcd")
}

我们来一下一个断点,compile文件在你自己的$GOROOT/pkg/tool中

运行 complie

dlv exec /usr/local/Cellar/go/1.13.4/libexec/pkg/tool/darwin_amd64/compile default.go

我们下第一个断点

(dlv)b cmd/compile/internal/gc.parseFiles
(dlv)c

跟踪到图中,我们可以看到这就是读取源码文件过程;

再下一个断点,跟踪到断点

(dlv)b cmd/compile/internal/syntax.Parse
 (dlv)c

我们可以看到parser结构

cmd/compile/internal/syntax/parser.go:17

type parser struct {
   file *PosBase   // 文件的信息(行、列)源位置
   errh ErrorHandler
   mode Mode
   scanner

   base   *PosBase // 当前位置及地址
   first  error    // 遇到的第一个错误
   errcnt int      // 遇到的错误数
   pragma Pragma  // pragma标志

   fnest  int     // 函数嵌套级别(用于错误处理)
   xnest  int     // 表达式嵌套级别(用于complit歧义解决)
   indent []byte  // 跟踪支持
}

cmd/compile/internal/syntax/scanner.go:30

type scanner struct {
   source
   mode   uint
   nlsemi bool // 如果设置为“\n”,则EOF转换为“;”

   // 当前token , 调用后next()函数后变为下一个token
   line, col uint
   tok       token
   lit       string   // valid if tok is _Name, _Literal, or _Semi ("semicolon", "newline", or "EOF")
   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
}

cmd/compile/internal/syntax/source.go:33

type source struct {
   src  io.Reader                          //os.Open的文件缓冲
   errh func(line, pos uint, msg string)   //错误提示

   // 缓冲区数据
   buf         [4 << 10]byte //2进制100左移10位1000000000000,对应的就是4096
   r0, r, w    int           // 以前/当前读写buf位置,不包括sentinel
   line0, line uint          // 之前/当前 行号
   col0, col   uint          // 之前/当前 列 (从行开始的字节偏移量)
   ioerr       error        // 挂起io错误

   // 文字缓冲区
   lit []byte // 文字前缀
   suf int    // 文字后缀; suf >= 0 意思是我们正在扫描一个文本
}

我们继续下跟,跟到cmd/compile/internal/syntax.(*scanner).next 中,next函数其实是不断的读取下一个字符。

看图中的打印,*cmd/compile/internal/syntax.scanner中的source中的buf其实就存储来我们的源代码;

ident字符串处理流程:

我们代码解析对应的token在src/cmd/compile/internal/syntax/tokens.go中。

RUN TESTING测试:

cd $GOROOT/src/cmd/compile/internal/syntax

添加一个代码:

然后修改scanner_test.go中的TestScanner的打开文件

执行命令:

#cd  $GOROOT/src/cmd/compile/internal/syntax
#go test -run=TestScanner

TOKEN解析过程;

总结词法解析扫码过程:

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章