Ruby 2.x 源代碼學習:詞法分析

前言

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

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;
}
相關文章
相關標籤/搜索