Skip to content

Commit 56e5fed

Browse files
committed
go-compiler 词法分析部分
1 parent 293b22f commit 56e5fed

File tree

1 file changed

+244
-0
lines changed

1 file changed

+244
-0
lines changed

compilers.md

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
# 编译原理
2+
> 本文主要介绍编译的几个主要过程及周边工具的使用, 对于工具内部具体实现的算法不做分析, 感兴趣的可自行搜索
3+
4+
## 词法分析
5+
6+
```mermaid
7+
sequenceDiagram
8+
Source code->>Token stream: Lexical analysis
9+
Note left of Source code: 1 + (2 - 3) * 4 / 5
10+
Note left of Source code: SELECT * FROM TABLE1 LIMIT 1;
11+
Note left of Source code: {"key1": 1, "key2": "val", "key3": {}}
12+
#------
13+
Note right of Token stream: 1<br/>+<br/>(<br/>2<br/>-<br/>3<br/>)<br/>*<br/>4<br/>/<br/>5
14+
Note right of Token stream: SELECT<br/>*<br/>FROM<br/>TABLE1<br/>LIMIT<br/>1<br/>;
15+
Note right of Token stream: {<br/>"key1"<br/>:<br/>1<br/>,<br/>"key2"<br/>:<br/>"val"<br/>,<br/>"key3"<br/>:<br/>{<br/>,<br/>}<br/>,<br/>}<br/>
16+
```
17+
18+
第一步将源代码处理成为`token stream`, 这边的源代码可以是一段简单的`go`代码, `DML`, `DSL`, 甚至是`JSON`格式的文件或者其他文本内容等等, `Lexical analysis`的目的就是按照某个定义规则将文本处理成为一连串的`token stream`
19+
> 标记 / Token: 指处理好后的一个字串, 是构成源代码的最小单位, 比如我们可以归类 golang 中的关键字, 例如 var、const、import 等等, 或者一个字符串变量 "str" 或者操作符 :=、>=、== 等等,只要是符合我们定义的语法规则处理后出现的字串, 都可以称为一个 token
20+
21+
如上图, 左边框内的三条源代码案例, 经过词法分析后, 可能会(具体看自己对`token`的定义处理规则)输出右边的三块`token stream`(每一行代表一个`token`)
22+
23+
### lex / flex
24+
lex / flex 是常用的词法分析器,支持正则表示某类 token
25+
26+
flex 文件完整格式:
27+
```c
28+
%{
29+
Declarations
30+
%}
31+
Definitions
32+
%%
33+
Rules
34+
%%
35+
User subroutines
36+
```
37+
38+
例:
39+
test.l
40+
```c
41+
42+
/* Declarations */
43+
%{
44+
void yyerror(const char *msg);
45+
%}
46+
47+
48+
/* Definitions */
49+
WHITESPACE ([ \t\r\a]+)
50+
OPERATOR ([+*-/%=,;!<>(){}])
51+
INTEGER ([0-9]+)
52+
53+
54+
/* Rules */
55+
%%
56+
57+
{WHITESPACE} { /* void */ }
58+
59+
{OPERATOR} { printf("%s\n", yytext); }
60+
61+
{INTEGER} { printf("%d\n", atoi(yytext)); }
62+
63+
\n { /* void */ }
64+
65+
. { printf("analysis error: unknow [%s]\n", yytext); exit(1); }
66+
67+
%%
68+
69+
/* User subroutines */
70+
int main(int argc, char* argv[]) {
71+
FILE *fp = NULL;
72+
if (argc == 2) {
73+
fp = fopen(argv[1], "r");
74+
if (fp) {
75+
yyin = fp;
76+
}
77+
}
78+
yylex();
79+
if (fp) {
80+
fclose(fp);
81+
}
82+
return 0;
83+
}
84+
85+
int yywrap(void) {
86+
return 1;
87+
}
88+
89+
void yyerror(const char *msg) {
90+
fprintf(stderr, "Error :\n\t%s\n", msg);
91+
exit(-1);
92+
}
93+
```
94+
95+
96+
以上小段词法分析代码定义了三种`token`:`WHITESPACE`, `OPERATOR`, `INTEGER`, 分别用正则定义了他们的规则, 而后在 `Rules` 规则阶段分别对这三种 `token` 进行了各自的处理
97+
```shell
98+
# 编译
99+
flex -o test.c test.l
100+
gcc -std=c89 -o flextest test.c
101+
./test.c test.txt
102+
```
103+
而后用根据我们定义的规则生成的词法分析器`flextest`来处理一个简单的案例
104+
105+
```shell
106+
cat test.txt
107+
1 + (2 - 3) * 4 / 5 sss
108+
109+
./flextest ./test.txt
110+
1
111+
+
112+
(
113+
2
114+
-
115+
3
116+
)
117+
*
118+
4
119+
/
120+
5
121+
analysis error: unknow [s]
122+
```
123+
根据输出的`token stream`可以看到, 能通过`token`规则处理的字串会完成输出一个成功处理的`token`, 规则之外的则处理失败
124+
125+
经过以上的小案例, 那么如果让我们自己来做一个`golang`的词法分析 `token` 的定义, 难度就不会特别大了
126+
127+
这边可以来简单看下`golang`编译器源码内的`token`定义
128+
129+
```go
130+
// src/go/token/token.go
131+
var tokens = [...]string{
132+
ILLEGAL: "ILLEGAL",
133+
134+
EOF: "EOF",
135+
COMMENT: "COMMENT",
136+
137+
IDENT: "IDENT",
138+
INT: "INT",
139+
FLOAT: "FLOAT",
140+
IMAG: "IMAG",
141+
CHAR: "CHAR",
142+
STRING: "STRING",
143+
144+
ADD: "+",
145+
SUB: "-",
146+
MUL: "*",
147+
QUO: "/",
148+
REM: "%",
149+
150+
AND: "&",
151+
OR: "|",
152+
XOR: "^",
153+
SHL: "<<",
154+
SHR: ">>",
155+
AND_NOT: "&^",
156+
157+
ADD_ASSIGN: "+=",
158+
SUB_ASSIGN: "-=",
159+
MUL_ASSIGN: "*=",
160+
QUO_ASSIGN: "/=",
161+
REM_ASSIGN: "%=",
162+
163+
AND_ASSIGN: "&=",
164+
OR_ASSIGN: "|=",
165+
XOR_ASSIGN: "^=",
166+
SHL_ASSIGN: "<<=",
167+
SHR_ASSIGN: ">>=",
168+
AND_NOT_ASSIGN: "&^=",
169+
170+
LAND: "&&",
171+
LOR: "||",
172+
ARROW: "<-",
173+
INC: "++",
174+
DEC: "--",
175+
176+
EQL: "==",
177+
LSS: "<",
178+
GTR: ">",
179+
ASSIGN: "=",
180+
NOT: "!",
181+
182+
NEQ: "!=",
183+
LEQ: "<=",
184+
GEQ: ">=",
185+
DEFINE: ":=",
186+
ELLIPSIS: "...",
187+
188+
LPAREN: "(",
189+
LBRACK: "[",
190+
LBRACE: "{",
191+
COMMA: ",",
192+
PERIOD: ".",
193+
194+
RPAREN: ")",
195+
RBRACK: "]",
196+
RBRACE: "}",
197+
SEMICOLON: ";",
198+
COLON: ":",
199+
200+
BREAK: "break",
201+
CASE: "case",
202+
CHAN: "chan",
203+
CONST: "const",
204+
CONTINUE: "continue",
205+
206+
DEFAULT: "default",
207+
DEFER: "defer",
208+
ELSE: "else",
209+
FALLTHROUGH: "fallthrough",
210+
FOR: "for",
211+
212+
FUNC: "func",
213+
GO: "go",
214+
GOTO: "goto",
215+
IF: "if",
216+
IMPORT: "import",
217+
218+
INTERFACE: "interface",
219+
MAP: "map",
220+
PACKAGE: "package",
221+
RANGE: "range",
222+
RETURN: "return",
223+
224+
SELECT: "select",
225+
STRUCT: "struct",
226+
SWITCH: "switch",
227+
TYPE: "type",
228+
VAR: "var",
229+
}
230+
```
231+
232+
## 语法分析
233+
根据第一步[词法分析](#词法分析)我们目前已经获取到了自源代码处理好之后的一个`token stream`, 在语法分析阶段主要负责的就是把这一串「看似毫无规则」的标记流进行语法结构上的处理
234+
例如
235+
1.判断某个赋值操作是否可以执行, 赋值号两边的变量及数据类型是否匹配
236+
2.运算规则是否符合语法规则
237+
3.语句优先级
238+
……
239+
在这个阶段可以直接翻译成目标代码, 或者生成诸如语法树之类的数据结构以便后续语义分析,优化等阶段利用。
240+
> 上下文无关文法: 文法中所有的产生式左边只有一个非终结符
241+
> https://www.zhihu.com/question/21833944
242+
243+
### bison
244+
### goyacc

0 commit comments

Comments
 (0)