Ruby 沒有使用 LEX 來實現詞法分析,而是選擇本身手寫詞法分析器,結合 YACC(BISON)實現語法分析,相關的源代碼在 parse.y(YACC語法描述)文件中緩存
parse.y 中的 parser_yylex 是詞法分析器的入口,函數的末尾調用 parse_indent 解析標識符less
static int parser_yylex(struct parser_params *parser) { ... parse_ident(parser, c, cmd_state); }
先來看看 parse_ident 函數開始的局部變量聲明:ide
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(詞法分析器)內部的狀態code
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 加入到標識符內部緩存中token
parser_is_identchar:判斷字符 c 是不是合法的標識符字符串
pushback:回退字符cmd
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 結構體封裝了關鍵字緩衝池
因爲代碼是使用 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; }