2016-05-15 26 views
1

我正在嘗試爲AMPL語言的一些子集構建詞法分析器。 我需要現在什麼類型的符號名稱詞法分析器正在處理。 每個符號名稱都是var或param或set。幸運的是,它們都必須在使用之前進行申報。所以我想我可以在詞法分析器使用先行運營商在柔性簡單地改變代碼如何在flex中查看下一個令牌而不將其從緩衝區中移除

SYMBOLIC_NAME [a-zA-Z_][a-zA-Z0-9_]* 
%% 
param    { return PARAM; } 
var    { return VAR; } 
set    { return SET; } 
{SYMBOLIC_NAME} { yylval.string = (char*) strdup(yytext); 
        return SYMBOLIC_NAME; 
        } 
%% 

一些這樣的事

SYMBOLIC_NAME [a-zA-Z_][a-zA-Z0-9_]* 
%{ 
#include <vector> 
#include <algorithm> 
std::vector<std::string> paramNames; 
std::vector<std::string> setNames; 
std::vector<std::string> varNames; 

%} 
%% 
param/(.|\n)+{SYMBOLIC_NAME}    { paramNames.push_back(&yytext[5]); 
              return PARAM; } 
var/(.|\n)+{SYMBOLIC_NAME}    { varNames.push_back(&yytext[3]); 
              return VAR; } 
set/(.|\n)+{SYMBOLIC_NAME}    { setNames.push_back(&yytext[3]); 
              return SET; } 
{SYMBOLIC_NAME} { if (std::find(setNames.begin(), setNames.end(), yytext) != setNames.end()) { 
        yylval.string = (char*) strdup(yytext); 
        return SET_NAME; 
        } 

        if (std::find(paramNames.begin(), paramNames.end(), yytext) != paramNames.end()){ 
        yylval.string = (char*) strdup(yytext); 
        return PARAM_NAME; 
        } 
        if (std::find(varNames.begin(), varNames.end(), yytext) != varNames.end()){ 
        yylval.string = (char*) strdup(yytext); 
        return VAR_NAME; 
        } 
       } 
%% 

我知道這是行不通的,因爲yytext中不包含第二前三個正則表達式的一部分。 問題出現在(。| \ n)+ {SYMBOLIC_NAME}之下。

PS

我知道代碼是不是最佳的,但它是不是在這裏的一個問題:d

回答

2

我想你想檢查一個符號表,看看你看到的是什麼樣的名字。

如果是這樣的話,你應該通過符號表進行溝通。那就是:

  1. 創建一個簡單的「符號」規則。你原來的規則是好的:

    {SYMBOLIC_NAME} { yylval.string = (char*) strdup(yytext); 
           return SYMBOLIC_NAME; 
           } 
    
  2. 在解析器級報關企業辦理報關語法:

    var_decl : VAR SYMBOLIC_NAME 
        { add name to symbol table } 
    
  3. 現在回去,延長SYMBOLIC_NAME規則來檢查定義的符號:

    {SYMBOLIC_NAME} { 
          yylval.string = (char*) strdup(yytext); 
    
          if (std::find(setNames.begin(), setNames.end(), yytext) != setNames.end()) { 
           return SET_NAME; 
          } 
          else if (... varNames ...) { 
           return VAR_NAME; 
          else if (... paramNames ...) { 
           return PARAM_NAME; 
          } 
          else { 
           return SYMBOLIC_NAME; 
          } 
         } 
    

現在你有一個Flex目標返回四個可能的標記,d依賴於定義。但是Flex不必擔心記住哪些符號定義是活動的 - 您可以讓解析器處理它。

在解析器側,你寫不同的規則:

var_decl: VAR SYMBOLIC_NAME 
set_decl: SET SYMBOLIC_NAME 

expr: atom '+' atom 
atom: VAR_NAME | SET_NAME | PARAM_NAME 
+0

太棒了。感謝您的解決方案 – Lisu

1

你可以有效地做到了「偷看」使用起始條件,但如果你實際上想do是維護一個符號表,並且讓詞法分析器爲每個符號自動返回正確的語義類別,這裏有一個更好的解決方案(見下文)。

首先,不是使用三個std::vector列表,而是直接搜索每一個,直到找到符號,使用單個std::unordered_map將每個名稱與語義類型相關聯真的會更好。 (我知道你說不要考慮代碼的低效率,但這種改變使事情變得相當簡單)。

如果你想要詞法分析器負責維護符號表,那很容易儘管這樣做有點困難,因爲解析器還需要存儲與每個符號相關的語義信息。儘管如此,這並不太痛苦。下面我使用一個開始條件在定義關鍵字之後收集定義的名稱(這基本上就是您的前瞻所做的事情,但這樣,詞法分析器就會隔離實際定義的名稱,而不是以可變數量的空白開始的字符串)。

這裏我利用哈希表包含代表符號名稱的std::string這一事實,通過將yylval.string設置爲來自哈希表條目的內部C字符串。只要您不修改yylval.string的內容,這是非常安全的,因爲符號永遠不會從符號表中刪除,並且哈希表永遠不會移動其元素。在實踐中,這很可能是更明智的yylval工會成員:

%union { 
    std::string* string; 
    // ... 
} 

但是這是一個小細節。這裏的掃描儀:

%{ 
    #include <unordered_map> 
    namespace { 
    enum class Kind { UNDEFINED, PARAM, SET, VAR }; 
    std::unordered_map<std::string, Kind> symbols; 
    } 
%} 
%x SC_DEFINE 

id [[:alpha:]_][[:alnum:]_]* 

%% 
    /* Up to the first unindented line is inserted at the beginning of yylex */ 
    Kind to_define; 

<*>[[:space:]] /* Ignore in all start conditions */ 
param   { kind_to_define = Kind::PARAM; BEGIN(SC_DEFINE); } 
set   { kind_to_define = Kind::SET; BEGIN(SC_DEFINE); } 
var   { kind_to_define = Kind::VAR; BEGIN(SC_DEFINE); } 
{id}   { auto it = symbols.emplace(yytext, Kind::UNKNOWN).first; 
       yylval.string = it->first.c_str(); 
       switch (it->second) { 
        case Kind::PARAM: return PARAM_NAME; 
        case Kind::SET: return SET_NAME; 
        case Kind::VAR: return VAR_NAME; 
        default:   return UNDEFINED_NAME; 
       } 
       } 
<SC_DEFINE>{id} { auto itbool = symbols.emplace(yytext, to_define); 
        if (!itbool.second) { 
        if (itbool.first->second != Kind::UNKNOWN) { 
         /* Redefinition: handle the error somehow */ 
        } else { 
         /* Used previously, error presumably already issued */ 
         itbool.first->second = to_define; 
        } 
        } 
        BEGIN(INITIAL); 
        yylval.string = itbool.first->first.c_str(); 
        switch (to_define) { 
        case Kind::PARAM: return DEFINE_PARAM; 
        case Kind::SET: return DEFINE_SET; 
        case Kind::VAR: return DEFINE_VAR; 
        default: /* Logic error */ 
        } 
       } 

注:以上回報DEFINE_PARAM(例如)令牌,該令牌已經指示符號的名稱(在yylval.string),讓你的語法規則必須是,例如,param_definition: DEFINE_PARAM ...而不是param_definition: PARAM SYMBOL ...

有一件事我沒有做到以上就是填寫在SC_DEFINE啓動條件中的其他項:任何其他令牌(大概)一個語法錯誤,其中包括你碰巧需要(如varset任何關鍵字標記和例如param)。

我認爲這將工作,雖然我沒有真正試圖編譯它。但它不僅有點笨重。

恕我直言,更好的方法是在解析器和詞法分析器之間共享符號表。 (野牛手冊解釋瞭如何爲yylex提供額外的參數,flex手冊解釋瞭如何接收它們。)基本符號表看起來與上面相同,只是它屬於解析器,或者更準確地說是程序調用解析器。但是,它可能會有更多的語義信息,而不僅僅是符號類型。映射的值可能是一個有區別的聯盟,一個boost::variant或其他任何證明方便的地方。

在這種情況下,掃描儀將如上所述,但沒有啓動條件。當它看到一個未定義的符號(因爲它應該掃描符號時,下面的定義關鍵字),它會返回一個UNDEFINED_NAME的道理,所以你的解析器的規則看起來是這樣的:

param_definition: PARAM UNDEFINED_NAME ... 

,並在語義動作定義,解析器將填充符號的種類以及任何其他有用的信息。在這種情況下,將yylval.symbol作爲指向符號表中值的指針(而不是迭代器,可能會失效,而是&*iterator這是穩定的),以便查找不需要重複。

在這種情況下,使用未定義的符號並嘗試定義已定義的符號將自然是語法錯誤,因爲解析器將沒有處理這些情況的規則。要提供有意義的錯誤消息,您可能需要添加諸如錯誤情況之類的規則。

0

你不應該在語法在意。您的語法應該只處理IDENTIFIER,然後查找語義操作的符號表本身。

在詞法分析器中所有這些都是錯誤的做法。

相關問題