显然,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扩展的更多信息,请参考更多的资料:) 很快,他们就会满大街都是了。
先来讨论字符流转换成符号流的部分,由于这部分不是讨论的重点,就利用了目前已经相当通用的技术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扩展的更多信息,请参考更多的资料:) 很快,他们就会满大街都是了。