現在編譯器處理那種「用換行代替分號」的語句邏輯是怎麼做的?

原本以為很簡單,今天在寫Kotlin的Parser的時候簡直想砸牆。

因為換行既是Whitespace也是end of statement,有可能會被stringToken給吃掉,換行就沒了,我還怎麼判斷語句結束?

話題加了個Haskell是因為我這個Parser是Haskell寫的。


你可以把字元串先斷行,然後一行一行來。

對於JS那種,你一行parse完發現還不能結束,就接另一行。

對於VB哪種,你一行parse完發現有_,就強制接另一行。

Kotlin不知道是哪種,反正你自己看著辦吧,都很簡單。


Kotlin語言的語法精確定義隱藏在源碼中,一部分在tokener, 一部分在 parser.

使用回車做語句結束比較複雜,只有在:

不是一個字元串的延續

不是一個聲明語句的結束

不是賦值語句的結束

不是函數參數的結束

光是靠語言描述就很長,何況是代碼。

所以,不先寫出精確的語法描述,寫這些語言的Parser, 會讓你的腦袋成為一鍋粥。


我感覺你可以去看一下語言標準,一個靠譜的語言都會說明的,什麼時候算分格什麼時候算省略分號


在 Scala 里,滿足這三個條件的換行符會作為語句的結尾:

  1. 換行符前的 Token 能作為語句的結尾
  2. 換行符後的 Token 能作為語句的開頭
  3. Token 出現在允許多條語句的區域中

字面量,標識符,這些關鍵字

  • this
  • null
  • true
  • false
  • return
  • type
  • &

以及這些分隔符

  • _
  • )
  • ]
  • }

都能作為語句的結尾部分。

而除了這些關鍵字

  • catch
  • else
  • extends
  • finally
  • forSome
  • match
  • with
  • yield

還有這些分隔符

  • ,
  • .
  • ;
  • :
  • =
  • =&>
  • &<-
  • &<:
  • &<%
  • &>:
  • #
  • [
  • )
  • ]
  • }

剩下的的 Token 都能作為語句的開頭

=========

先去睡覺了,明天繼續寫


某語言使用嚴格的縮進…

在標準某語言里,語句只分為複合句和簡單句,簡單句只佔一行,複合句開頭的位置一定要和當前縮進級別相吻合。

使用分行的話,其實就是忽略NEWLINE和INDENT這些token。

這種過於簡單的設計,天生地製造了表達式和語句的天塹。

有時覺得簡直就像一種新的位元組碼,一板一眼。


我 知 道 了

我發現

吃掉前面的Whitespace就沒有這些破事了

請大家原諒我的無知


向前探測一下,不就知道了嗎?符合機器狀態的下個符號,推入,不符合的就不推了。編譯器都會容錯的,一遍編譯儘可能的發現所有編譯錯誤


用parsec簡單粗暴的解決的話,可以自己寫一個處理換行符的組合子來做sepBy,這樣在一個 statement 正常結束之後就可以通過分號或者換行來分隔下一句

isSpaceNotNewline c = isSpace c c /= "
" c /= "
"
-- Cannot use `lexeme` or `space` since they have already included newline
lineBreaker :: Parser String
lineBreaker = many $ skipMany (satisfy isSpaceNotNewline) &>&> endOfLine

stmtBreaker :: Parser String
stmtBreaker = try semi &<|&> lineBreaker

stmtSep :: Parser a -&> Parser [a]
stmtSep p = lexeme $ p `sepBy` stmtBreaker


token 拿來一個換行符,parser能找到一個匹配就是語句結束唄,找不到就是要忽略的唄。

比如

a = b(c,

d)

顯然你吃到換行的時候完不成任何匹配,因此你忽略換行接著下一行啊。

然鵝

a = b

b = c

你吃到換行的時候, 換行是你某個產生式的組成部分, 正常吃,所以a=b就處理完了,所以你接著下一行唄。

如果你產生式都正確的話,那麼把換行當一個token,然後這個不就是「expected里沒有換行token的時候吃到換行token不報錯」 嗎?


就把"
"當成";"用唄

發現沒有結束就繼續解析下一行

或者發現行末字元前是:就繼續解析直到限定符(比如end)出現唄


我有一個不成熟的小建議

咱們學js吧 末尾加分號 hhh

(有點 沒審題hhh )

那先用預處理器處理一邊全加分號 然後再parse?


推薦閱讀:

程序如何根據變數名在內存中找到存放這個變數的地址?
如何減小GacLib生成的可執行文件大小?
OCaml pattern有哪些葵花寶典?
C語言if與else if寫成的這樣一段代碼效率上或者編譯完成後的結構上是否有區別(主要看補充內容中的詳細)?
如何實現 C 語言編譯器?

TAG:Haskell | 編譯原理 | 編譯器 | Kotlin |