Lia*_*vis 4 c regex parsing interpreter lexical-analysis
我正在尝试编写一种解释性编程语言,它将读入文件并输出类似字节码的格式,然后由虚拟机执行.
我最初的计划是:
IF myvalue = 5 THEN将成为IF myvalue 5,IF即将成为0x09),IF或0x09后跟两个字节).我被告知正则表达式是一种可怕的方式,但我不确定这是否是实现解释语言的好或坏方法.
这主要是为了体验,所以我不介意它是否不完全是性能友好的,但这将是一个好处.
实现我的解释器的最佳方法是什么,是否有任何例子(用简单的旧C编写)?
人们告诉你正则表达式不是这种情况的最佳选择的原因是因为正则表达式需要更多的时间来评估,而正则表达式语言有许多限制和怪癖使它不适合许多应用程序.
你应该知道,这只是许多程序员(包括我自己)的一种下意识反应,无论正则表达式是否真的适合应用程序.这源于人们试图用正则表达式做太多,例如尝试解析HTML.
许多编译器使用基本的单通道标记化算法.标记生成器有一个非常基本的概念,即可以用作分隔符,应该如何处理常量和标识符等.然后,标记生成器将快速迭代输入并发出一串令牌,然后可以轻松解析.
对于基本应用程序,例如解析令牌,使用正则表达式的相对较小的惩罚不是值得担心的.然而正如我所说,有时候正则表达式如何工作可能会限制你的tokenizer可以做的事情,尽管这项工作通常可以卸载到编译器管道中的后续点.遗传算法应该做的所有事情都应该由标准的正则表达式来表示.
应该注意的是,当你直接在你的代码中包含正则表达式时,事情会变得有些毛茸茸.您必须确定如何将正则表达式应用于输入,输入将被描绘,等等.您还将承担编译和评估正则表达式的惩罚.
有些项目,例如lex,使用正则表达式,因为它们提供了一种简单,简洁的方式来描述语法,然后它可以在选择的任何表示中内部使用.他们还将为您处理所有的胶合逻辑,您只需要通过正则表达式描述它应该使用的语法.
当使用这样的生成器时,它可以将任何正则表达式更改为代表表达式实际含义的代码.如果它看到了表达式[0-9],它只能用调用isdigit,等效switch语句或其他表示来替换它.这使得生成的代码比正则表达式的任何内联使用都更有效.
所以我的想法是这样的:如果你想使用正则表达式来分析你的语言,一路过关斩将,创造一个扫描器描述flex/ lex为您生成的标记生成器.但是,如果您实际上是自己完全编写它,那么使用我所描述的逻辑上更简单的方法会更好.
我认为编写一个不使用正则表达式的示例标记器会很有趣,所以在这里.我用C-like C++编写它.我使用的唯一C++功能是标准向量和字符串,但我这样做是为了让你可以轻松地放入C变种.
#include <vector>
#include <ctype.h>
#include <string>
typedef std::vector<std::string> string_list;
typedef std::vector<long long > int_list;
typedef std::vector<long double> float_list;
std::string substr(const char* value, size_t length){
std::string v;
v.resize(length);
memcpy(&v[0], value, length * sizeof(char));
return v;
}
long long string_to_int(const char* value, size_t length){
return atoll(substr(value, length).c_str());
}
long double string_to_float(const char* value, size_t length){
return atof(substr(value, length).c_str());
}
void int_list_add(int_list& list, long long value){
list.push_back(value);
}
void string_list_add(string_list& list, const char* value, size_t length){
list.push_back(substr(value, length));
}
void float_list_add(float_list& list, long double value){
list.push_back(value);
}
size_t int_list_last(int_list& list){
return list.size();
}
size_t string_list_last(string_list& list){
return list.size();
}
size_t float_list_last(float_list& list){
return list.size();
}
typedef struct{
string_list identifiers;
string_list constants_string;
int_list constants_int;
float_list constants_float;
size_t id;
} *state, state_value;
state tok_state_create(){
state ret = new state_value;
ret->id = 0;
return ret;
}
void tok_state_destroy(state t_state){
delete t_state;
}
const char* tok_state_read_identifier(state t_state, size_t id){
return t_state->identifiers[id - 1].c_str();
}
const char* tok_state_read_string(state t_state, size_t id){
return t_state->constants_string[id - 1].c_str();
}
long long tok_state_read_int(state t_state, size_t id){
return t_state->constants_int[id - 1];
}
long double tok_state_read_float(state t_state, size_t id){
return t_state->constants_float[id - 1];
}
const char* punct_tokens[] = { "Not A Token (Dummy)",
".", ",", "<", "<<", ">", ">>",
";", "+", "-", "/", "*", "!", "%", "^",
"&", "(", ")", "=", "==", "[", "]", "{",
"}", "?", ":", "|", "||", "&&", "~", 0
};
const char* key_tokens[] = { "Not A Token (Dummy)",
"if", "while", "do", "then", "end", 0
};
typedef enum{
TOK_TYPE_INTEGER = 500,
TOK_TYPE_FLOAT,
TOK_TYPE_STRING,
TOK_TYPE_IDENTIFIER,
TOK_TYPE_NONE
} tok_type;
const char* get_token_from_id(size_t id){
if (id < 100){
return punct_tokens[id];
}
if (id < 200){
return key_tokens[id - 100];
}
if (id >= 500){
switch (id){
case TOK_TYPE_INTEGER: return "Integer Constant";
case TOK_TYPE_FLOAT: return "Float Constant ";
case TOK_TYPE_STRING: return "String Constant ";
case TOK_TYPE_IDENTIFIER: return "Identifier ";
case TOK_TYPE_NONE: return "Unknown ";
default:
break;
}
}
return "Not A Token (Dummy)";
}
int is_identifier_char(char c){
if (isalpha(c) || c == '_'){
return 1;
}
return 0;
}
size_t read_punct_token(const char* input, size_t size){
size_t max_len = 0;
size_t token_id = 0;
for (size_t i = 1; punct_tokens[i] != 0; ++i){
size_t len = strlen(punct_tokens[i]);
if (len > max_len && len <= size && strncmp(punct_tokens[i], input, len) == 0){
max_len = len;
if (i == 1 && size > 1 && isdigit(input[1])){
return 0; //Special case for floats
}
token_id = i;
}
}
return token_id;
}
size_t read_key_token(const char* input, size_t size){
size_t max_len = 0;
size_t token_id = 0;
for (size_t i = 1; key_tokens[i] != 0; ++i){
size_t len = strlen(key_tokens[i]);
if (len > max_len && len <= size && strncmp(key_tokens[i], input, len) == 0){
max_len = len;
token_id = i + 100;
}
}
return token_id;
}
size_t is_punct_token_char(char c){
for (size_t i = 1; punct_tokens[i] != 0; ++i){
if (punct_tokens[i][0] == c){
return 1;
}
}
return 0;
}
void add_token(state t_state, tok_type type, const char* string, size_t length){
switch (type){
case TOK_TYPE_INTEGER:
int_list_add(t_state->constants_int, string_to_int(string, length));
t_state->id = int_list_last(t_state->constants_int);
break;
case TOK_TYPE_FLOAT:
float_list_add(t_state->constants_float, string_to_float(string, length));
t_state->id = float_list_last(t_state->constants_float);
break;
case TOK_TYPE_STRING:
string_list_add(t_state->constants_string, string, length);
t_state->id = string_list_last(t_state->constants_string);
break;
case TOK_TYPE_IDENTIFIER:
string_list_add(t_state->identifiers, string, length);
t_state->id = string_list_last(t_state->identifiers);
break;
default:
//Do some error here
break;
}
}
size_t get_token(state t_state, char** input, size_t *size){
if (t_state->id != 0){
size_t id = t_state->id;
t_state->id = 0;
return id;
}
char* base = *input;
size_t padding = 0;
size_t length = 0;
tok_type type = TOK_TYPE_NONE;
while (*size > 0){
if (isspace(*base)){
base++;
(*size)--;
}
else{
break;
}
}
size_t tok = read_punct_token(base, *size);
if (tok){
size_t len = +strlen(get_token_from_id(tok));
*input = base + len;
*size -= len;
return tok;
}
tok = read_key_token(base, *size);
if (tok){
size_t len = +strlen(get_token_from_id(tok));
*input = base + len;
*size -= len;
return tok;
}
while (*size - length > 0){
if (length == 0 && type == TOK_TYPE_NONE){
if (is_identifier_char(*base)){
type = TOK_TYPE_IDENTIFIER;
length++;
}
else if (*base == '"'){
type = TOK_TYPE_STRING;
padding = 1;
base++;
(*size)--;
}
else if (*base == '.' && *size > 1 && isdigit(base[1])){
type = TOK_TYPE_FLOAT;
}
else if (isdigit(*base)){
type = TOK_TYPE_INTEGER;
}
else if (is_punct_token_char(*base)){
tok = read_punct_token(base, *size);
if (tok){
size_t len = strlen(punct_tokens[tok]);
*input += len;
*size -= len;
return tok;
}
else{
//do error
}
}
}
else{
if (!isspace(base[length]) || type == TOK_TYPE_STRING){
switch (type){
case TOK_TYPE_INTEGER:
if (isdigit(base[length])){
length++;
continue;
}
else if (base[length] == '.' || tolower(base[length]) == 'e'){
type = TOK_TYPE_FLOAT;
length++;
continue;
}
break;
case TOK_TYPE_FLOAT:
if (isdigit(base[length]) || base[length] == '.' || base[length] == 'e'){
length++;
continue;
}
break;
case TOK_TYPE_STRING:
if (base[length] != '"'){
length++;
continue;
}
break;
case TOK_TYPE_IDENTIFIER:
if (is_identifier_char(base[length])){
length++;
continue;
}
break;
default:
break;
}
}
//We only get here if this is a space or any of the switch cases didn't continue.
add_token(t_state, type, base, length);
*input = base + length + padding;
*size -= length + padding;
return type;
}
}
*input = base + length + padding;
*size -= length + padding;
return 0;
}
int main(){
const char* input = "if(1+1==4)then print\"hi!\";end";
state s = tok_state_create();
size_t size = strlen(input);
size_t token;
size_t token_prev = 0;
printf("Token\tMeaning\n\n");
while ((token = get_token(s, (char**)&input, &size)) != 0){
if (token_prev < 500){
if (token < 500){
printf("%d\t%s\n", token, get_token_from_id(token));
}
else{
printf("%d\t%s #", token, get_token_from_id(token));
}
}
else{
printf("%d\t", token);
switch (token_prev){
case TOK_TYPE_IDENTIFIER: printf("%s\n", tok_state_read_identifier(s, token)); break;
case TOK_TYPE_STRING: printf("%s\n", tok_state_read_string(s, token)); break;
case TOK_TYPE_INTEGER: printf("%d\n", tok_state_read_int(s, token)); break;
case TOK_TYPE_FLOAT: printf("%f\n", tok_state_read_float(s, token)); break;
}
}
token_prev = token;
}
tok_state_destroy(s);
}
Run Code Online (Sandbox Code Playgroud)
这将打印:
Token Meaning
101 if
16 (
500 Integer Constant #1 1
8 +
500 Integer Constant #2 1
19 ==
500 Integer Constant #3 4
17 )
104 then
503 Identifier #1 print
502 String Constant #1 hi!
7 ;
105 end
Run Code Online (Sandbox Code Playgroud)