簡體   English   中英

解決LALR歧義

[英]Resolving LALR Ambiguities

我最近裹着我的頭周圍LALR足夠寫一LALR發生器 ,和我試圖構建它基於Java或C#的語法風格(它的開始被指定在這里 )。

我知道編寫解析器生成器需要付出額外的努力,比如重新發明輪子(為什么不使用Antlr?),但我的目標是引導一個可以自行編譯而不依賴於第三方工具鏈的業余愛好操作系統。 我的問題雖然不是生成器,而是語法。

我正在使用語句和表達式來減少/減少歧義。

我知道如何解決某些類型的歧義,比如懸掛其他東西,但是這幾個對我來說並不直觀,而且讓我感到難過。

解決這些問題的最佳方法是什么? 此外,是否有可用於幫助可視化解決方案的原型制作工具? 或者,我應該回到原點並嘗試為語法實現GLR解析器生成器嗎?

這些陳述是合法的:

Generic.List<int> myVar1 = x + 4, myVar2; // stmt -> var-decl ;
                                          // var-decl -> type-name var-decl-list

t = 99;                           // simple-stmt -> assign

i++;                              // simple-stmt -> incr-decr
                                  // incr-decr -> primary-expr ++

json.deserialize<list<int>>(obj); // simple-stmt -> call
                                  // call -> primary-expr ( params )
                                  // ...  -> primary-expr . basic-name ( params )
                                  // ...  -> basic-name . basic-name ( params )

這是它的設置方式:

basic-name : ident < type-list >
           | ident

nested-name : nested-name . basic-name
            | basic-name

basic-type : int | bool | ...

type-name : nested-name
          | basic-type

stmt : var-decl ;
     | simple-stmt ;
     | ...

var-decl : type-name var-decl-list

var-decl-list : var-decl-list , var-init
              | var-init

var-init : ident assign-op expression
         | ident

simple-stmt : assign
            | call
            | incr-decr

expr : assign-expr

assign-expr : assign
            | cond-expr

assign : unary-expr assign-op expr

...
rel-expr : rel-expr < shift-expr
         ...
         | shift-expr

...
unary-expr : unary-op primary-expr
           | primary-expr

unary-op : + - ! ~ ++ --  // Prefix operators
         | ( type-name )  // Conversion operator

primary-expr : call
             | primary-expr . basic-name
             | primary-expr ++
             | ( expr )
             ...
             | basic-name

call : primary-expr ( params )

incr-decr : primary-expr ++
          | -- primary-expr
          | ...

因此,當解析器期望一個語句時,* LR(k)項集內核是method-body -> { * stmts-opt }並且為該狀態設置的完整項看起來像這樣:

method-body -> { * stmts-opt }
stmts-opt -> * stmts
stmts-opt -> *
stmts -> * stmts stmt
stmt -> * var-decl ;
stmt -> * simple-stmt ;
var-decl -> * type-name var-decl-list
simple-stmt -> * assign
simple-stmt -> * call
simple-stmt -> * incr-decr
type-name -> * nested-name
type-name -> * basic-type
nested-name -> * nested-name . basic-name
nested-name -> * basic-name
basic-name -> * ident < type-list >
basic-name -> * ident
assign -> * unary-expr assign-op expr
unary-expr -> * unary-op primary-expr
unary-expr -> * primary-expr
unary-op -> * ( typename )
unary-op -> * ! | ~ | ...
primary-expr -> * call
primary-expr -> * primary-expr . basic-name
primary-expr -> * primary-expr ++
primary-expr -> * basic-name
primary-expr -> * ( expr )
call -> * primary-expr ( params )
incr-decr -> * primary-expr ++
...

當標識符被移位時,下一個狀態是:

basic-name -> ident *
basic-name -> ident * < type-list >

解析或減少,並將下一個狀態帶到:

nested-name -> basic-name *
primary-expr -> basic-name *

潛在的沖突。 在父狀態中,前瞻不起作用,因為nested-nameprimary-expr有一個點。 哦,好的,讓我們嘗試通過嵌套名稱減少:

type-name -> nested-name *
nested-name -> nested-name * . basic-name

這里沒有什么可看的......現在,如何通過primary-expr減少:

unary-expr -> primary-expr *
primary-expr -> primary-expr * . basic-name
primary-expr -> primary-expr * ++
call -> primary-expr * ( params )
incr-decr -> primary-expr * ++
...

現在當我們轉換++時,我們得到:

primary-expr -> primary-expr ++ *
incr-decr -> primary-expr ++ *

......另一種減少 - 減少沖突。

讓我們嘗試改變(而不是ident

primary-expr -> ( * expr )
unary-op -> ( * type-name )
expr -> * assign-expr
assign-expr -> * assign
assign-expr -> * cond-expr
assign -> * unary-expr assign-op expr
unary-expr -> * unary-op primary-expr
unary-expr -> * primary-expr
unary-op -> * ( typename )
unary-op -> * ! | ~ | ...
primary-expr -> * call
primary-expr -> * primary-expr . basic-name
primary-expr -> * primary-expr ++
primary-expr -> * basic-name
primary-expr -> * ( expr )
call -> * primary-expr ( params )
cond-expr -> * ...
...
rel-expr -> * rel-expr < shift-expr
rel-expr -> * shift-expr
...
type-name -> * nested-name
type-name -> * basic-type
nested-name -> * nested-name . basic-name
nested-name -> * basic-name
basic-name -> * ident < type-list >
basic-name -> * ident

移動ident(在堆棧上)時ident同樣的問題。

這些只是我到目前為止遇到的問題。 由於basic-name優先於rel-expr ,我假設x < n將被解釋為basic-name -> ident < type-list * ,如果它實際上是關系表達式則會出錯。

我的大腦已經達到了我可以真正使用一些幫助的程度。

您的帖子中有一些問題,這使得它不太適合SO。 但我會嘗試提供一些關於每個人的想法。 在我看來,你有三個問題:

  1. 區分表達式語句與非語句的表達式。

  2. 在聲明中解析分層命名的類型,而不與表達式語句中的字段訪問表達式沖突

  3. 區分<作為比較運算符和模板括號的使用。


1.區分表達式語句與非語句的表達式。

據我所知,希望只允許具有(或可能具有)某種副作用的語句表達式:賦值,增量變量和子程序調用。 粗略地說,這對應於Java,其語法包括:

StatementExpression:
  Assignment
  PreIncrementExpression
  PreDecrementExpression
  PostIncrementExpression
  PostDecrementExpression
  MethodInvocation
  ClassInstanceCreationExpression

StatementExpression列出的每個備選方案出現在Expression的派生樹中的某個位置,其中它們已經從可能性列表中分解出來。 這是一個非常簡潔的樣本:

Expression:
  LambdaExpression
  AssignmentExpression

AssignmentExpression:
  ConditionalExpression
  Assignment

Assignment:
  LeftHandSide AssignmentOperator Expression

...

UnaryExpression:
  PreIncrementExpression
  + UnaryExpression
  UnaryExpressionNotPlusMinus

PreIncrementExpression:
  ++ UnaryExpression

UnaryExpressionNotPlusMinus:
  PostfixExpression
  ~ UnaryExpression

PostfixExpression:
  Primary
  ExpressionName
  PostIncrementExpression

PostIncrementExpress:
  PostfixExpression ++

請注意ExpressionStatement右側使用的非終端如何在每個優先級別進行特殊設置。 在C ++語法中,不限制哪些表達式可以是語句,不需要單獨的Assignment非終端:

assignment-expression:
  conditional-expression
  logical-or-expression assignment-operator initializer-clause

但在Java中,這是行不通的。 它需要創建額外的非終端,它不會導出ConditionalExpression ,正是為了讓Assignment成為一個StatementAssignmentExpression一個Expression

2.在聲明中解析分層命名的類型,而不與表達式語句中的字段訪問表達式沖突

與上面類似,這里有必要從其他形式的字段訪問表達式中放置分層名稱(必須以basic-name開頭),這些表達式可能以任何(其他) primary-expr開頭。 前者可以是類型名稱或主要表達式; 后者只能是類型名稱。 為了做出這種區分,我們需要拆分primary-expr生產:

primary-expr : field-access-expr
             | nested-name

non-field-access-expr:
               call
             | post-increment-expression  // was primary-expr ++
             | ( expr )
             ...

field-access-expr :
               non-field-access-expr
             | field-access-expr . basic-name

3.區分<作為比較運算符和模板括號的使用。

與其他兩個問題不同,這個問題實際上可能是語言中的含糊不清。 例如,在C ++中,模板括號肯定是模棱兩可的; 它們只能通過知道(或被告知)特定名稱是否是模板名稱來解決。 另一方面,在Java中,有時需要類型參數在通用名稱之前來解決歧義。 例如:

ConstructorDeclarator:
  [TypeParameters] SimpleTypeName ( [FormalParameterList] )

要么

MethodInvocation:
  Primary . [TypeArguments] Identifier ( [ArgumentList] )

在C#中,還有一個不同的規則,它需要查看>可能與開頭<匹配的字符。 該算法在C#手冊的第7.6.4.2節中描述; 我不知道你將如何在LALR(1)解析器中實現它。

暫無
暫無

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

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