[英]How to define simple programming language grammar that mix statements and expressions grammar just like Scala/Rust?
[英]Grammar for if statements in newline sensitive language
我正在研究一種類似於英語的語言,並且在if
語句的語法上有問題。 如果您好奇,該語言的靈感來自HyperTalk ,所以我試圖確保我匹配該語言中的所有有效結構。 我正在使用的示例輸入演示了所有可能的if
構造可以在 這里查看。 有很多,所以我不想內聯代碼。
我已經從語法中刪除了大多數其他結構,使其更易於閱讀,但基本上語句如下所示:
start
: statementList
;
statementList
: '\n'
| statement '\n'
| statementList '\n'
| statementList statement '\n'
;
statement
: ID
| ifStatement
;
我看到的移位/減少沖突在 ifStatement 規則中:
ifStatement
: ifCondition THEN statement
| ifCondition THEN statement ELSE statement
| ifCondition THEN statement ELSE '\n' statementList END IF
| ifCondition THEN '\n' statementList END IF
| ifCondition THEN '\n' END IF
| ifCondition THEN '\n' ELSE statement
| ifCondition THEN '\n' ELSE '\n' statementList END IF
| ifCondition THEN '\n' statementList ELSE statement
| ifCondition THEN '\n' statementList ELSE '\n' statementList END IF
// The following rules cause issues, but should be legal:
| ifCondition THEN statement newlines ELSE statement
| ifCondition THEN statement newlines ELSE '\n' statementList END IF
;
ifCondition
: IF expression
| IF expression '\n'
;
expression
: TRUE
| FALSE
;
newlines
: '\n'
| newlines '\n'
;
問題是我需要支持這個結構:
if true then statement # <- Any number of newlines
else statement
問題(據我所知)是沒有足夠的上下文來正確確定是否移動else
或僅減少if true then statement
部分而不知道后面會發生什么(語句列表的末尾或另一個語句)。 這甚至可以解析嗎?
做到這一點非常困難,因此我嘗試對這些步驟進行注釋。 有很多煩人的細節。
從本質上講,這只是懸空 else 歧義的一種表現,其解決方案是眾所周知的(強制解析器始終移動else
)。 下面的解決方案解決了語法本身的歧義,這是明確的。
我在這里使用的基本原則是幾十年前 Alfred Aho 和 Jeffrey Ullman 在《編譯器設計原則》中概述的原則(所謂的“龍之書”,我之所以提到這本書,是因為它的作者最近獲得了圖靈獎。以及他們其他有影響力的作品)。 特別是,我使用術語“匹配”和“不匹配”(而不是“開放”和“封閉”,它們也很流行),因為這就是我學習它的方式。
也可以使用優先聲明來解決這個語法問題; 事實上,事實證明這通常要簡單得多。 但是在這種特殊情況下,使用運算符優先級並不容易,因為相關標記( else
)可以在任意數量的換行標記之前。 我很確定你仍然可以構建一個基於優先級的解決方案,但是使用明確的語法有很多好處,包括易於移植到不使用相同優先級算法的解析器生成器,以及它是可以進行機械分析。
解決方案的基本大綱是將所有語句分為兩類:
else
子句擴展語句。 (換句話說,每個if…then
都由一個對應的else
匹配。)這些else
子句擴展。 (換句話說,至少有一個if…then
子句不被else
匹配。)由於不匹配的語句是完整的語句,它不能緊跟else
標記; 如果出現了else
標記,它將有助於擴展語句。 一旦我們設法為這兩類語句構造了語法,只需要弄清楚歧義語法中哪些statement
的用法可以跟隨else
。 在所有這些上下文中,必須將非終結statement
替換為非終結matched-statement
,因為只有匹配語句才能跟在else
后面而不與它交互。 在其他情況下,如果else
不能成為下一個標記,則任一類別的語句都是有效的。
所以基本的語法風格是(取自龍書):
stmt → matched_stmt
| unmatched_stmt
matched_stmt → "if" expr "then" matched_stmt "else" matched_stmt
| other_stmt
unmatched_stmt → "if" expr "then" matched_stmt "else" unmatched_stmt
| "if" expr "then" stmt
other_stmt
不是條件語句。 或者,更准確地說,除了以stmt
結尾的復合語句之外的任何內容。
據我所知,在 Hypertalk 中, if
語句是唯一可以以語句結尾的復合語句。 其他復合語句以end X
精確終止,這有效地關閉了語句。 但是在其他語言中,例如C,復合語句有很多種,其中大部分需要根據它們的終止子語句是(遞歸地)匹配還是不匹配來划分為“匹配”和“不匹配”。
我想在這里指出的一件事,如果你稍微看一下,從大綱語法中可以明顯看出, if
語句的if…then…else
部分在語法上類似於括號前綴運算符。 也就是說, matched_stmt
和unmatched_stmt
都類似於一元減法的右遞歸規則:
unary → '-' unary
| atom
反過來,它可以用擴展 BNF 方言寫成,允許 Kleene 明星作為
unary → ('-')* atom
如果我們要對 Aho&Ullman 的語法進行這種轉換,我們最終會得到:
if_then_else → "if" expr "then" matched_stmt "else"
matched_stmt → (if_then_else)* other_stmt
unmatched_stmt → (if_then_else)* "if" expr "then" stmt
這使得如何使用自上而下的遞歸下降解析器來實現這個語法變得相當清楚。 (需要一點左分解,但它最終仍然類似於一元減法語法。)我不打算在這個答案中進一步發展這個想法,但我認為 EBNF 轉換有助於指導直覺這個語法實際上是如何解開其他的。
這對於弄清楚如何處理換行符也很有幫助。 關鍵的見解(對我來說)是語句必須以換行符結尾。 一個例外是if
命令的精簡單行版本。 但是該異常僅發生在else
標記之前(並且僅當它在同一行上匹配的then
時)。 在這個語法中,這種情況是用inner-matched
的非終結符實現的,這得益於單行語句(如do-statement
)缺少終止換行符這一事實。 終止單行語句的換行符添加到matched
( single-statement NL
)的遞歸基本情況中; 這是唯一需要處理的地方。 多行復合語句都使用終止換行符定義(例如,請參見repeat-statement
)。
大多數 rest 的並發症處理各種句法 forms。 唯一真正有趣的是在一行末尾的then
標記之后處理塊。 該塊可以通過兩種方式終止:
end if
,沒有else
子句。 這被視為“匹配”的情況,因為它顯然不能用else
子句擴展。else
子句(可以是單行else
或塊else
,其中else
標記位於行尾)。 但是這里可能存在歧義。 如果塊中的最后一條語句是不匹配的if
,則else
行應該擴展該語句,而不是終止該塊。 這與匹配/不匹配邏輯的 rest 並沒有太大區別; 為了實現它,我創建了兩個不同的block
非終結符,一個以匹配語句結尾,另一個以不匹配語句結尾。 然后,像往常一樣,只能在else
之前使用匹配的塊。 (我發現 bison 3.7.6 中的新反例生成器在這里非常有用;我最初的嘗試只是使用了block
,因為我沒有注意到歧義。但這是一個真正的歧義,它導致了一個起源於 shift-reduce 的沖突看起來很神秘。一旦我看到由反例生成器生成的反例——它顯示了在if-then
發生在block
內的沖突——問題變得更加明顯。)
matched-block
和unmatched-block
匹配塊之間的交替是語法產生式和 state 機器之間對應的簡單示例。 兩個非終結符代表了一個非常簡單的 state 機器中的兩種狀態,其 state 記錄了一個位:最后一條語句是否匹配。 非終結符必須是右遞歸才能工作,這與構建 LALR(1) 語法的通常“首選左遞歸”啟發式方法不同。
好的,前言太長了,這里是語法。 為了緊湊化,我將表達式簡化為變量和 boolean 常量,僅包含一個簡單語句 ( do expr ) 並僅包含另一個復合語句 ( repeat until expr / block / end repeat )。 (最后一個是占位符。)
program : block
block : %empty
| matched-block
| unmatched-block
NL : '\n'
| NL '\n'
matched-block
: block matched
unmatched-block
: block unmatched
simple-statement
: "do" expression
repeat-statement
: "repeat" "until" expression NL block "end" "repeat" NL
matched : if-then matched else-matched
| if-then inner-matched else-matched
| if-then NL matched-block else-matched
| if-then NL else-matched
| if-then NL block "end" "if" NL
| repeat-statement
| simple-statement NL
inner-matched
: %empty
| simple-statement
| if-then inner-matched "else" inner-matched
unmatched
: if-then matched
| if-then unmatched
| if-then inner-matched "else" unmatched
| if-then matched "else" unmatched
if-then : "if" expression NL "then"
| "if" expression "then"
else-matched
: "else" NL block "end" "if" NL
| "else" matched
expression
: ID
| "true"
| "false"
之間有明顯的歧義
ifCondition THEN statement EOL ELSE statement
和
ifCondition THEN EOL statementList ELSE statement
回顧
statement: %empty
statementList: statement
結果是statement
和statementList
都可以導出空序列。 因此ifStatement
的上述兩種產生式都可以推導出:
ifCondition THEN EOL ELSE statement
解析器無法知道EOL
之前是否有空statement
或之后是否有空statementList
。 (您可能不在乎選擇了哪一個,但解析器對這種決定很着迷。)
可空的產生式通常是有問題的。 在可能的情況下,避免使用它們。 不要讓statement
派生為空,而是通過添加省略可選語句的規則來明確指出空語句可能在 go 的位置。 並考慮重寫statementList
,使其必須以EOL
結尾,我認為這是你的意圖(但也許我錯了)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.