11

我已經寫了一個生成器來實現這個技巧,但我想知道實現偏離規則的最佳方法。你將如何執行偏離規則?

不久:Off-side rule意味着在這種情況下,縮進被認爲是一個句法元素。

這裏是僞越位規則進行斷詞,在可用的形式捕捉壓痕,我不想用語言來限制答案:

token NEWLINE 
    matches r"\n\ *" 
    increase line count 
    pick up and store the indentation level 
    remember to also record the current level of parenthesis 

procedure layout tokens 
    level = stack of indentation levels 
    push 0 to level 
    last_newline = none 
    per each token 
     if it is NEWLINE put it to last_newline and get next token 
     if last_newline contains something 
      extract new_level and parenthesis_count from last_newline 
      - if newline was inside parentheses, do nothing 
      - if new_level > level.top 
       push new_level to level 
       emit last_newline as INDENT token and clear last_newline 
      - if new_level == level.top 
       emit last_newline and clear last_newline 
      - otherwise 
       while new_level < level.top 
        pop from level 
        if new_level > level.top 
         freak out, indentation is broken. 
        emit last_newline as DEDENT token 
       clear last_newline 
     emit token 
    while level.top != 0 
     emit token as DEDENT token 
     pop from level 

comments are ignored before they are getting into the layouter 
layouter lies between a lexer and a parser 

這製圖器在不產生超過一個NEWLINE時間,並且在縮進出現時不會生成NEWLINE。因此解析規則很簡單。我覺得這很不錯,但是告知是否有更好的方法來完成它。

雖然使用了這一段時間,但我注意到,在DEDENTs之後,無論如何都可以很好地發出newline,這樣,您可以將NEWLINE的表達式分開,同時將INDENT DEDENT作爲表達的預告片。

回答

8

在過去幾年裏,我已經爲幾種以縮進爲中心的領域特定語言編寫了標記器和解析器,並且您在那裏看到的東西對我來說看起來相當合理,無論值多少錢。如果我沒有弄錯,那麼你的方法和Python所做的很相似,例如,它看起來應該有一定的權重。

將NEWLINE NEWLINE INDENT轉換爲INDENT在命中解析器之前絕對看起來像是正確的做事方式 - 在解析器中始終在前面窺視它是一種痛苦(IME)!實際上,我已經將這一步作爲一個單獨的層來完成,最終成爲一個三步過程:第一個步驟結合了您的詞法分析器和layouter所做的減去所有NEWLINE lookahead的東西(這使得它非常簡單),第二個(也非常簡單)層連續摺疊NEWLINE並將NEWLINE INDENT轉換爲INDENT(或實際上,COLON NEWLINE INDENT轉換爲INDENT,因爲在這種情況下,所有縮進塊總是以冒號開頭),那麼解析器就是第三個階段。但是對於我來說,按照您描述的方式進行操作也是非常有意義的,特別是如果您想將詞法分析器與layouter分開時,假設您使用的是代碼生成工具例如,按照慣例,製作詞法分析器。

我確實有有點更加靈活的縮進規則,留下基本解析器在需要時強制執行他們需要一個應用 - 下面的需要是在某些情況下有效,例如:

this line introduces an indented block of literal text: 
    this line of the block is indented four spaces 
    but this line is only indented two spaces 

INDENT/DEDENT標記不能很好地工作,因爲您最終需要爲每列縮進生成一個INDENT,並在返回時生成相同數量的DEDENT,除非您想方設法找出哪裏縮進級別最終會出現,這似乎並不像一個標記器那樣。在這種情況下,我嘗試了一些不同的事情,最後只在每個NEWLINE標記中存儲了一個計數器,以便爲以下邏輯行改變縮進(正值或負值)。 (每個標記還存儲所有尾隨空白,以防需要保留;對於NEWLINE,存儲的空白包括EOL本身,任何中間空白行和下一個邏輯行上的縮進。)根本沒有單獨的INDENT或DEDENT標記。讓解析器處理這個問題比嵌套INDENT和DEDENT要多一點,而且可能是一個複雜的語法,需要一個花哨的解析器生成器,但它並沒有我擔心的那麼糟糕,無論是。同樣,不需要解析器從NEWLINE向前看,看看是否有INDENT出現在這個方案中。

儘管如此,我認爲你會同意在標記器/佈局器中允許和保留所有瘋狂的空格,並讓解析器決定什麼是文字,什麼是代碼是一個不尋常的要求!例如,如果您只是想解析Python代碼,那麼您肯定不希望解析器受到縮進計數器的影響。你做事情的方式幾乎肯定是你的應用程序和其他許多方面的正確方法。雖然如果任何人有想法如何最好做這種事情,我顯然喜歡聽到他們......

3

我最近一直在試驗這個,我得出結論,爲我的需要至少,我希望NEWLINES標記每個「聲明」的結尾,不管它是否是最後一個聲明,也就是我在DEDENT之前需要換行符。

我的解決方案是打開它的頭,而不是NEWLINES標記行的末尾,我使用LINE令牌來標記行的開始。

我有一個詞法分析器,用於摺疊空行(包括僅包含註釋的行)併發出一個帶有關於最後一行縮進信息的LINE標記。然後我的預處理函數接受這個標記流,並在縮進改變的任何行之間添加INDENT或DEDENT「in」。所以

line1 
    line2 
    line3 
line4 

會給標流

LINE "line1" INDENT LINE "line2" LINE "line3" DEDENT LINE "line4" EOF 

這讓我寫的聲明明確語法產生,而不用擔心檢測報告結束時竟然以嵌套的,凹陷的子塊,東西結束如果您將NEWLINES(和DEDENTS)匹配,那麼這可能很難。

這裏是預處理,寫在O'Caml的核心:

match next_token() with 
     LINE indentation -> 
     if indentation > !current_indentation then 
      (
      Stack.push !current_indentation indentation_stack; 
      current_indentation := indentation; 
      INDENT 
     ) 
     else if indentation < !current_indentation then 
      (
      let prev = Stack.pop indentation_stack in 
       if indentation > prev then 
       (
        current_indentation := indentation; 
        BAD_DEDENT 
       ) 
       else 
       (
        current_indentation := prev; 
        DEDENT 
       ) 
     ) 
     else (* indentation = !current_indentation *) 
      let token = remove_next_token() in 
      if next_token() = EOF then 
       remove_next_token() 
      else 
       token 
    | _ -> 
     remove_next_token() 

我還沒有添加括號還支持,但應該是一個簡單的擴展。它確實,但是避免在文件末尾發出一個流浪的LINE。

+0

您的代碼無法發出多個DEDENT,它在EOF之前都沒有考慮過縮進。它對某些事物可能有用,但這些事情比括號支持更重要。 – Cheery 2009-06-04 09:31:51

1

標記者在紅寶石的樂趣:

def tokenize(input) 
    result, prev_indent, curr_indent, line = [""], 0, 0, "" 
    line_started = false 

    input.each_char do |char| 

    case char 
    when ' ' 
     if line_started 
     # Content already started, add it. 
     line << char 
     else 
     # No content yet, just count. 
     curr_indent += 1 
     end 
    when "\n" 
     result.last << line + "\n" 
     curr_indent, line = 0, "" 
     line_started = false 
    else 
     # Check if we are at the first non-space character. 
     unless line_started 
     # Insert indent and dedent tokens if indentation changed. 
     if prev_indent > curr_indent 
      # 2 spaces dedentation 
      ((prev_indent - curr_indent)/2).times do 
      result << :DEDENT 
      end 
      result << "" 
     elsif prev_indent < curr_indent 
      result << :INDENT 
      result << "" 
     end 

     prev_indent = curr_indent 
     end 

     # Mark line as started and add char to line. 
     line_started = true; line << char 
    end 

    end 

    result 
end 

確實爲兩個空間縮進唯一的工作。結果是類似於["Hello there from level 0\n", :INDENT, "This\nis level\ntwo\n", :DEDENT, "This is level0 again\n"]