Design AST: la chiamata è sia espressione che affermazione?

0

Sto progettando l'AST per un compilatore e ho scoperto che in realtà non so come rappresentare il nodo Call .

Attualmente, la parte pertinente di AST appare come questa (dove le frecce indicano l'ereditarietà):

ASTNode -> Expression  -> Call
        -> Declaration -> Statement -> Call
                       -> TypeDeclaration, etc...

Ad esempio,

// this is a declaration 
def f(x: Integer from 0 to 16) - > {
    // This is a call that is also a statement
    print(x)
}

Come faccio a riconciliare queste due definizioni di Call ? In questa particolare grammatica, l'unica espressione che è anche un'istruzione è Call . La lingua che sto utilizzando supporta solo l'ereditarietà singola.

C'è un modo per aggirare completamente questo problema senza compromettere la sicurezza del tipo di un AST eterogeneo?

Come nota a margine, qualsiasi lettura aggiuntiva che puoi raccomandare sarebbe ottima.

    
posta Straivers 04.05.2018 - 12:49
fonte

1 risposta

3

L'AST è un ponte tra la sintassi concreta e il modello semantico della tua lingua. Non deve conformarsi esattamente alla sintassi. In particolare, è generalmente OK se l'AST potrebbe teoricamente rappresentare strutture che non possono essere renderizzate nella sintassi. (In particolare, il linguaggio di markup di ReStructured Text è definito in termini di un modello di oggetto documento che è sostanzialmente più espressivo della sintassi effettiva.)

Pertanto, è possibile introdurre un nodo AST di istruzione-espressione, anche se la sintassi limita questa espressione a essere una chiamata. Questo è sensato se le chiamate di istruzioni e le chiamate di espressioni non devono essere trattate in modo diverso nella tua lingua.

Un esempio in cui le istruzioni e le espressioni devono essere trattate in modo diverso è una definizione di funzione in JavaScript. Approssimativamente, l'istruzione function foo() {} dichiara una variabile foo che contiene un oggetto funzione. La stessa definizione in un'espressione non introdurrà una variabile nell'ambito di inclusione e valuterà tale oggetto funzione.

Potrebbe quindi essere ragionevole rappresentarlo utilizzando diversi tipi, ad es. FunctionExpression contro FunctionStatement . Questo non deve comportare un'eccessiva duplicazione! Per esempio. la variante dell'istruzione potrebbe contenere un oggetto FunctionExpression . In alternativa, l'istruzione della funzione potrebbe essere immediatamente desugared in AST a qualcosa come Assign(Var("foo"), Function(...)) in cui il nodo Function AST deve solo modellare la variante di espressione.

Quindi nel tuo caso, l'introduzione di un nodo AST CallStatement che contiene solo il nodo AST di espressione Call potrebbe essere un approccio praticabile.

Nota che la sicurezza del tipo è relativa, comunque. Utilizzando l'ereditarietà per modellare i nodi AST, il sistema dei tipi consente nuovi tipi di nodi AST arbitrari purché conformi ad alcune interfacce. Per esempio. sarebbe possibile creare un sottotipo di espressione che funge da adattatore per qualsiasi dichiarazione, anche se ciò sembra avere poco senso nel modello. Se hai bisogno di garanzie più severe, dovrai evitare l'ereditarietà aperta e usare una lingua con tipi di tipi di somma più limitati.

    
risposta data 04.05.2018 - 16:23
fonte