La grammatica che hai collegato sembra incompleta e buggata (non menziona i letterali degli oggetti). Parliamo invece di questo . Qui, le parentesi si presentano in quattro regole:
ObjectLiteral ⇒
{ }
| { FieldList }
...
Block ⇒ { BlockStatements }
...
SwitchStatement ⇒
switch ParenthesizedExpression { }
| switch ParenthesizedExpression { CaseGroups LastCaseGroup }
...
NamedFunction ⇒ function Identifier FormalParametersAndBody
FormalParametersAndBody ⇒ ( FormalParameters ) { TopStatements }
La funzione e i casi di switch non entreranno mai in conflitto con il blocco e il letterale dell'oggetto, perché sono preceduti dalle parole chiave function
e switch
. Non appena il parser riconosce queste parole chiave, tutte le altre produzioni alternative possono essere scartate.
Tuttavia, sembra esserci un'ambiguità tra i blocchi e i letterali degli oggetti. Come deve essere analizzata la istruzione {}
? Questo potrebbe essere un letterale dell'oggetto vuoto o un blocco vuoto.
La grammatica lo impedisce rilevando se un'espressione è l'espressione iniziale dell'istruzione e fornisce produzioni diverse per ogni caso:
PrimaryExpression(normal) ⇒
SimpleExpression
| FunctionExpression
| ObjectLiteral
PrimaryExpression(initial) ⇒ SimpleExpression
Ciò non consente i valori letterali degli oggetti nella prima espressione e anche le definizioni di funzione - se un function
si verifica all'inizio di un'istruzione, è sempre un'istruzione di definizione di funzione, mai un'espressione di funzione.
Ciò rende la grammatica JavaScript non ambigua. Questo è importante per le grammatiche LR, poiché evita di ridurre / ridurre i conflitti.
Per i parser top-down (ad esempio, la discesa ricorsiva scritta a mano), potremmo lasciare la grammatica ambigua ma assegnare priorità a ciascuna alternativa. Per esempio. in un Statement and TopStatement, le alternative Block e FunctionDefinition dovrebbero sempre essere tentate prima dell'opzione ExpressionStatement.
Per parser generalizzati in grado di analizzare tutte le grammatiche context-free (non solo i sottogruppi LL o LR), le ambiguità non ostacolano il parser. A seconda del parser, o restituisce un albero di analisi arbitrario di tutti gli alberi di analisi validi (in pratica indesiderabili) o itera su tutti i possibili alberi di analisi. Una grammatica effettivamente ambigua non è buona per i linguaggi di programmazione, poiché di solito vogliamo scrivere programmi che non possono essere fraintesi dal compilatore.
Tuttavia, esiste un'interessante classe di grammatiche non ambigue che non possono essere analizzate dai parser LR e richiede un parser generalizzato. Qui abbiamo un conflitto di riduzione / riduzione tra le regole A
e B
, ma è solo un'ambiguità locale e viene risolto più avanti nel programma:
Top = X | Y
X = A Balanced x
Y = B Balanced y
A = a b
B = a b
Balanced = x | y | () | ( Balanced ) | ( Balanced Balanced )
Poiché la regola Balanced è una grammatica context-free in sé, nessuna quantità di lookahead risolverà tutto ciò. L'input ab((y()))x
ha esattamente un albero di analisi (non è ambiguo), ma non è analizzabile dai parser comuni. Quando si progetta una lingua, dovrebbero essere evitate anche le ambiguità locali.