How To Build a Yacc?(6)

显然,Compiler至少分为两个明显的部分:一部分是读入源代码,将其转换成符号流,一部分是读入语法规则文件,生成DFA。

先来讨论字符流转换成符号流的部分,由于这部分不是讨论的重点,就利用了目前已经相当通用的技术lex。

如果要想在ruby环境中利用lex工具生成的c代码,只有把c代码封装成ruby的扩展库。

lex怎么工作的?

首先编写一个lex的输入文件:
// prog.l

%{
#include <string.h>
#include "prog.h"
char token_string[MAX_ID_LENGTH];
%}

whitespace         [ /t]+
newline            /n
digit             [0-9]
number             [+-]?{digit}+(/.{digit}+)?
bool            true|false
lbrace            "("
rbrace            ")"
semicolon         ";"
comma            ","
assignment        "="
string            /"[^"]*/"
comment         ////.*{newline}
letter            [a-zA-Z]
identifier      {letter}(/_|{letter}|{digit})*
constant        {bool}|{number}|{string}

%%

{lbrace}        { return LBRACE; }
{rbrace}        { return RBRACE; }
function        { return FUNCTION; }
{semicolon}        { return SEMICOLON; }
{comma}            { return COMMA; }
{assignment}    { return ASSIGNMENT; }
{identifier}    { return IDENTIFIER; }
{constant}        { return CONSTANT; }
{whitespace}    { }
{comment}       { }
{newline}       { }
.               { return ERR; }

%%

int yywrap(void)
{
    return 1;
}



int get_next_token()
{
    int t_id = yylex();
    strcpy(token_string, yytext);
    return t_id;
}

输入文件分三部分,第1部分是%{ %}之间的代码,纯粹的C代码,将被copy到目标C文件中,接下来是正则表达式定义;第2部分是模式,表示匹配表达式需要执行什么操作。第3部分是几个 C函数,最终也是被copy到目标C文件中,其中最核心的就是get_next_token()了,这个是提供给外部的函数。

关于lex的更多信息,需要参考更多的参考书,满大街都是。

好了,基础的知道了解这么多就够了,不要忘了我们的游戏规则:测试优先。那么,假若有了这样一个lex的封装如何使用它?

lex = Lex.new(src)
while (true)
    token = lex.get_next_token
    ts = lex.get_token_string
    assert(token == current_token && ts == current_token_string)
    if (token == EOF) break
end

那么我们的Lex类需要至少提供两个方法:
get_next_token取得下一个符号
get_token_string取得当前识别符号的字符串

Lex类是一个ruby的扩展类,创建这个扩展类的方法如下:
1) 按prog.l的规则生成prog.c
flex -t prog.l >prog.c
2) prog.h定义一些constant和外部接口

#ifndef PROG_H_
#define PROG_H_
#define MAX_ID_LENGTH 256
enum {LBRACE = 1, RBRACE = 2, FUNCTION=3, SEMICOLON=4,
COMMA=5, ASSIGNMENT= 6, IDENTIFIER=7, CONSTANT=8, ERR=9};
extern char token_string[];
int get_next_token(void);
#endif /*PROG_H_*/


3) 编写ruby扩展程序lex.c
// lex.c
#include <ruby.h>
#include <string.h>
#include "prog.h"

extern FILE* yyin;
 
static VALUE lex_init(VALUE self, VALUE file)
{
    long length = 0 ;
    char* name = rb_str2cstr(file, &length);
    yyin = fopen(name, "r");
      rb_iv_set(self, "@file", file);
      return self;
}


static VALUE lex_get_next_token(VALUE self)
{   
    VALUE t = INT2NUM(get_next_token());
    return t;
}

static VALUE lex_get_token_string(VALUE self)
{
    VALUE ts = rb_str_new2(token_string);
    return ts;   
}


static VALUE cTest;

void __declspec(dllexport)
Init_lex() {
      cTest = rb_define_class("Lex", rb_cObject);
      rb_define_method(cTest, "initialize", lex_init, 1);
      rb_define_method(cTest, "get_next_token", lex_get_next_token, 0);
      rb_define_method(cTest, "get_token_string", lex_get_token_string, 0);
}

4) 编写extconf.rb
require 'mkmf'
dir_config('lex')
create_makefile("lex")

5) 生成makefile
ruby extconf.rb --with-lex-dir=[include path]

6) 运行nmake ,生成lex.so

这些步骤顺利进行以后,只需要require 'lex.so', 就拥有了一个好用的Lex类。

关于如何编写ruby扩展的更多信息,请参考更多的资料:) 很快,他们就会满大街都是了。
阅读更多
换一批

没有更多推荐了,返回首页