前言
Ruby 没有使用 LEX 来实现词法分析,而是选择自己手写词法分析器,结合 YACC(BISON)实现语法分析,相关的源代码在 parse.y(YACC语法描述)文件中
解析标识符
parse.y 中的 parser_yylex 是词法分析器的入口,函数的末尾调用 parse_indent 解析标识符
static int parser_yylex(struct parser_params *parser) {
...
parse_ident(parser, c, cmd_state);
}
先来看看 parse_ident 函数开始的局部变量声明:
static int parse_ident(struct parser_params *parser, int c, int cmd_state) {
int result = 0;
const enum lex_state_e last_state = lex_state;
ID ident;
}
result 用于保存该函数返回给 YACC 的语法单元标识,它可以是 tIDENTIFIER, tCONSTANT 或者 tLABEL
last_state 用于保存 LEX(词法分析器)内部的状态
ident 用于保存标识符在 Ruby 解释器 的内部表示(索引)
函数的一开始使用 do-while 循环来收集组成标识符的字符
do {
if (!ISASCII(c)) mb = ENC_CODERANGE_UNKNOWN;
if (tokadd_mbchar(c) == -1)
return 0;
c = nextc();
} while (parser_is_identchar());
if ((c == '!' || c == '?') && !peek('=')) {
tokadd(c);
} else {
pushback(c);
}
tokfix();
tokadd_mbchar,tokadd: 将字符 c 加入到标识符内部缓存中
parser_is_identchar:判断字符 c 是否是合法的标识符
pushback:回退字符
tokfix:在标识符内部缓存末尾添加 0(C语言中的字符串结束符)
我们先略过 parse_ident 函数中关于 关键字 和其它内容判断,看看函数末尾:
ident = tokenize_ident(parser, last_state);
if (!IS_lex_state_for(last_state, EXPR_DOT|EXPR_FNAME) &&
(result == tIDENTIFIER) && /* not EXPR_FNAME, not attrasgn */
lvar_defined(ident)) {
SET_LEX_STATE(EXPR_END|EXPR_LABEL);
}
return result;
tokenize_ident 用于将标识符添加到解释器内部的符号表
static ID tokenize_ident(struct parser_params *parser, const enum lex_state_e last_state) {
ID ident = TOK_INTERN();
set_yylval_name(ident);
return ident;
}
TOK_INTERN 是一个宏定义:
# parse.y
#ifdef RIPPER
#define intern_cstr(n,l,en) rb_intern3(n,l,en)
#else
#define intern_cstr(n,l,en) rb_intern3(n,l,en)
#endif
#define TOK_INTERN() intern_cstr(tok(), toklen(), current_enc)
tok 和 toklen 的定义,可以在(从 parse.y 生成)parse.c 中找到
#define tokbuf (parser->tokenbuf)
#define toklen (parser->tokidx)
#define tok() tokenbuf
#define toklen() tokidx
使用宏定义来访问结构体或函数的代码风格在 Ruby 源代码中随处可见~
我们接着来看 rb_intern3 函数
# symbol.c
ID rb_intern3(const char *name, long len, rb_encoding *enc) {
VALUE sym;
struct RString fake_str;
VALUE str = rb_setup_fake_str(&fake_str, name, len, enc);
OBJ_FREEZE(str);
sym = lookup_str_sym(str);
if (sym) return rb_sym2id(sym);
str = rb_enc_str_new(name, len, enc); /* make true string */
return intern_str(str, 1);
}
rb_setup_fake_str 创建一个 FAKE Ruby String 对象(结构体)RString
lookup_str_sym 使用 创建出来的 RString 在符号表里查找 符号(sym)
如果找到 sym,将 sym 转化为 ID 直接返回
否则调用 rb_enc_str_new 创建一个"真实"的 str 并调用 intern_str 函数插入到符号表中
我们再回到 tokenize_ident 函数:
static ID tokenize_ident(struct parser_params *parser, const enum lex_state_e last_state) {
ID ident = TOK_INTERN();
set_yylval_name(ident);
return ident;
}
在调用 TOK_INTERN 宏将标识符保存到符号表之后,set_yylval_name(ident) 设置 yylval:
#ifndef RIPPER
...
# define set_yylval_name(x) (yylval.id = (x))
...
#else
...
解析关键字
关键字相关的操作主要在 lex.c 源代码文件中,lex.c 文件头部的注释显示该文件是使用 gperf 自动生成的
/* C code produced by gperf version 3.0.4 */
/* Command-line: gperf -C -P -p -j1 -i 1 -g -o -t -N rb_reserved_word -k'1,3,$' defs/keywords */
关键字缓冲池 stringpool_t
stringpool_t 结构体封装了关键字缓冲池
由于代码是使用 gperf 自动生成的,所以会有一些 hard code 的数字 str8 .etc
struct stringpool_t
{
char stringpool_str8[sizeof("break")];
char stringpool_str9[sizeof("else")];
char stringpool_str10[sizeof("nil")];
char stringpool_str11[sizeof("ensure")];
char stringpool_str12[sizeof("end")];
char stringpool_str13[sizeof("then")];
char stringpool_str14[sizeof("not")];
char stringpool_str15[sizeof("false")];
char stringpool_str16[sizeof("self")];
char stringpool_str17[sizeof("elsif")];
char stringpool_str18[sizeof("rescue")];
char stringpool_str19[sizeof("true")];
char stringpool_str20[sizeof("until")];
char stringpool_str21[sizeof("unless")];
char stringpool_str22[sizeof("return")];
char stringpool_str23[sizeof("def")];
char stringpool_str24[sizeof("and")];
char stringpool_str25[sizeof("do")];
char stringpool_str26[sizeof("yield")];
char stringpool_str27[sizeof("for")];
char stringpool_str28[sizeof("undef")];
char stringpool_str29[sizeof("or")];
char stringpool_str30[sizeof("in")];
char stringpool_str31[sizeof("when")];
char stringpool_str32[sizeof("retry")];
char stringpool_str33[sizeof("if")];
char stringpool_str34[sizeof("case")];
char stringpool_str35[sizeof("redo")];
char stringpool_str36[sizeof("next")];
char stringpool_str37[sizeof("super")];
char stringpool_str38[sizeof("module")];
char stringpool_str39[sizeof("begin")];
char stringpool_str40[sizeof("__LINE__")];
char stringpool_str41[sizeof("__FILE__")];
char stringpool_str42[sizeof("__ENCODING__")];
char stringpool_str43[sizeof("END")];
char stringpool_str44[sizeof("alias")];
char stringpool_str45[sizeof("BEGIN")];
char stringpool_str46[sizeof("defined?")];
char stringpool_str47[sizeof("class")];
char stringpool_str50[sizeof("while")];
};
stringpool_contents 变量是缓存池的一个实例:
static const struct stringpool_t stringpool_contents =
{
"break",
"else",
"nil",
"ensure",
"end",
"then",
"not",
"false",
"self",
"elsif",
"rescue",
"true",
"until",
"unless",
"return",
"def",
"and",
"do",
"yield",
"for",
"undef",
"or",
"in",
"when",
"retry",
"if",
"case",
"redo",
"next",
"super",
"module",
"begin",
"__LINE__",
"__FILE__",
"__ENCODING__",
"END",
"alias",
"BEGIN",
"defined?",
"class",
"while"
};
判断字符串是否是关键字
rb_reserved_word 函数判断 长度为 len 的字符串 str 是否是关键字
如果字符串的长度不在 关键字 长度区间内,直接返回 0
根据 str, len 计算 str 在 上面提到的 stringpool_contents 里面的索引(key)
如果 key 不在 范围内内,直接返回 0
比较字符串
const struct kwtable *rb_reserved_word(str, len) register const char *str;
register unsigned int len; {
if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) {
register int key = hash (str, len);
if (key <= MAX_HASH_VALUE && key >= 0) {
register int o = wordlist[key].name;
if (o >= 0) {
register const char *s = o + stringpool;
if (*str == *s && !strcmp (str + 1, s + 1))
return &wordlist[key];
}
}
}
return 0;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。