Ruby 2.x 源代码学习:词法分析

前言

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值