簡體   English   中英

如何在沒有正則表達式的情況下實現語言解釋器?

[英]How to implement a language interpreter without regular expressions?

我正在嘗試編寫一種解釋性編程語言,它將讀入文件並輸出類似字節碼的格式,然后由虛擬機執行。

我最初的計划是:

  • 首先將文件的內容加載到我的解釋器的內存中,
  • 讀取每一行(其中一個單詞被定義為一系列字母,后跟空格或轉義字符),
  • 使用正則表達式,確定單詞的參數,
  • 例如, IF myvalue = 5 THEN將成為IF myvalue 5
  • 將每個單詞轉換為字節形式(即IF將變為0x09 ),
  • 逐個執行這些字節(因為執行程序將理解IF0x09后跟兩個字節。

我被告知正則表達式是一種可怕的方式,但我不確定這是否是實現解釋語言的好或壞方法。

這主要是為了體驗,所以我不介意它是否不完全是性能友好的,但這將是一個好處。

實現我的解釋器的最佳方法是什么,是否有任何示例(用簡單的舊C編寫)?

人們告訴你正則表達式不是這種情況的最佳選擇的原因是因為正則表達式需要更多的時間來評估,而正則表達式語言有許多限制和怪癖使它不適合許多應用程序。

你應該知道,這只是許多程序員(包括我自己)的一種下意識的反應,無論正則表達式是否真的適合應用程序。 這源於人們試圖用正則表達式做太多,例如嘗試解析HTML。

許多編譯器使用基本的單通道標記化算法。 標記生成器有一個非常基本的概念,即可以用作分隔符,如何處理常量和標識符等。然后,標記生成器將快速迭代輸入並發出一串令牌,然后可以輕松解析。

對於基本應用程序,例如解析令牌,使用正則表達式的相對較小的懲罰不是值得擔心的。 然而正如我所說,有時候正則表達式如何工作可能會限制你的tokenizer可以做的事情,盡管這項工作通常可以卸載到編譯器管道中的后續點。 遺傳算法應該做的所有事情都應該由標准的正則表達式來表示。

應該注意的是,當你直接在你的代碼中包含正則表達式時,事情會變得有些毛茸茸。 您必須確定如何將正則表達式應用於輸入,輸入將被描繪,等等。 您還將承擔編譯和評估正則表達式的懲罰。

有一些項目,比如lex ,它們使用正則表達式,因為它們提供了一種簡單,簡潔的方式來描述語法,然后它可以在選擇的任何表示中內部使用。 他們還將為您處理所有的膠合邏輯,您只需要通過正則表達式描述它應該使用的語法。

當使用這樣的生成器時,它可以將任何正則表達式更改為代表表達式實際含義的代碼。 如果它看到表達式[0-9] ,它可以通過調用isdigit ,等效的switch語句或其他表示來替換它。 這使得生成的代碼比正則表達式的任何內聯使用都更有效。

所以我的想法是這樣的:如果你想使用正則表達式來解析你的語言,那就一直為flex / lex創建一個掃描器描述,為你生成tokenizer。 但是,如果您實際上是自己完全編寫它,那么使用我所描述的邏輯上更簡單的方法會更好。

我認為編寫一個不使用正則表達式的示例標記器會很有趣,所以在這里。 我用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);
}

這將打印:

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

正如其他人所提到的,正則表達式沒有任何問題。 這是一條典型的路徑:

  1. 將源文件解析為標記
  2. 從解析的標記流生成抽象語法樹
  3. 走你的AST來生成你的字節碼

Flex / bison是1/2的絕佳工具。

您可能還需要某種符號表來表示變量定義。

許多編譯器設計課程最終將為c語言的某個子集實現編譯器,您可以嘗試查看開放課件類型的站點以獲得實際的類。

正如其他答案所說,正則表達式本身沒有任何問題。 但更重要的是, 正則表達式是字符級語法的自然表示

所有主要(和許多(大多數?)次要語言)使用正則表達式來定義不同輸入令牌的外觀,至少作為設計語言的形式。

但是對於正則表達式庫提供的某些技巧,可能確實存在性能損失。 這是因為許多事情,如反向引用必須由功能較弱的自動機實現,而不是更純粹的正則表達式。

正則表達式(沒有諸如反向引用之類的花哨的東西)可以轉換為有限自動機或狀態機,並且可以用更簡單(即更快 )的控制功能來實現。

至少,為了提高效率,您應該嘗試預編譯定義表達式的語言,並使用已編譯的匹配器對象,而不是每次動態構建一個新對象。 如果你的正則表達式庫沒有提供這個,也許是時候去做一些圖書館購物,或者閱讀自動機理論。

復雜語言的任何解釋器或編譯器(“具有(...)或復合嵌套語句的表達式,如do-while,if-then-else”)要求您構建解析器以提取(通常是遞歸的)結構碼。

你在這里得到了很多答案,說“正則表達式對於令牌很有用”。 是的,在許多經典構建的編譯器和解釋器中,人們編寫正則表達式來描述單個標記的形狀(標識符,關鍵字,數字和字符串常量,注釋),並將它們交給詞法分析器生成器(如Flex或許多其他 ),它們結合在一起這些成為一個有效的有限狀態機。 這是有效的,因為令牌的“語法”幾乎總是非常簡單。 這非常簡單意味着您可以自己手動編寫令牌詞法分析器,並為中小型語言生成實際結果。 (在某些時候(例如,COBOL)語言的龐大規模開始壓倒你,如果你想要保持理智,很難避免使用詞法分析器)。

沒有討論的是真正的解析 ,發現結構假設我們已經以某種方式構建了令牌。 使用正則表達式來代替令牌。 並且正則表達式不能用於解析; 他們無法識別嵌套結構,這是那些“復雜”結構所需要的。 不熟悉解析的人會反復犯這個錯誤。

如果您想成功,您需要學習如何構建解析器。

對於復雜語言,有解析器生成器(YACC,JavaCC, 許多其他 ),與lexer生成器類似,它將采用BNF並為您生成解析器。 如果要編寫帶有此類工具的編譯器,則通常將解析器操作附加到語法規則識別點,通常用於構建樹以供以后處理。

您還可以為適度大小的語言手動編寫解析器代碼。 這是一組相互遞歸的過程,每個語法規則一個,用於分析代碼(使用遞歸來處理嵌套構造)。 您還可以將解析操作附加到過程。 由於這些過程識別語法結構,因此如果您使用解析器生成器,這實際上與您應用的技巧基本相同。 可以簡單地擴展該方案以處理令牌的“解析”(lexing)。 如果你完全沿着這條路走下去,那么任何地方都沒有正則表達式。

可以通過在語法規則識別位置/手動編碼的遞歸解析過程中執行解釋器動作來構建解釋器。 它不會運行得非常快,因為它會不斷重新解析源代碼。

最好構建一個表示程序(抽象)語法樹(AST) ,然后構建和解釋爬行AST來執行它 通過將樹構建操作附加為解析器操作來構造AST。

如果您想生成字節碼 ,那么您就會遇到經典的代碼生成問題。 通常最好通過構建AST來解決這些問題,幾乎可以像解釋器一樣遍歷AST,並吐出用於在每個點實現解釋器目的的字節代碼。 您可以通過在解析器操作中生成字節代碼來構建動態代碼生成器。 以這種方式生成“好”代碼更難,因為代碼生成器無法看到足夠的上下文來很好地處理特殊情況。

你可以摸索所有這些。 您最好的方法是獲取編譯器書籍,或者參加編譯器課程。 然后所有這一切都會更加清晰。 當然,如果你摸索了這一點,你將會更好地了解編譯人員用來做這件事的各種機器。 (谷歌我的論文“解析后的生活”)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM