2012-07-23 13 views
7

Sto scrivendo un programma in cui ho bisogno di analizzare un file sorgente JavaScript, estrarre alcuni fatti e inserire/sostituire porzioni del codice. Una descrizione semplificata dei tipi di cose che avevo bisogno di fare è, dato questo codice:Utilizzo di ANTLR per analizzare e modificare il codice sorgente; sto sbagliando?

foo(['a', 'b', 'c']); 

estratto 'a', 'b' e 'c' e riscrivere il codice come:

foo('bar', [0, 1, 2]); 

sto usando ANTLR per le mie esigenze di analisi, producendo codice C# 3. Qualcun altro aveva già contribuito con una grammatica JavaScript. L'analisi del codice sorgente sta funzionando.

Il problema che sto riscontrando è capire come analizzare e modificare correttamente il file sorgente. Ogni approccio che cerco di prendere in realtà risolvendo il problema mi porta in un vicolo cieco. Non posso fare a meno di pensare che non sto usando lo strumento come è inteso o sono solo troppo novizio quando si tratta di trattare con gli AST.

Il mio primo approccio è stato quello di analizzare utilizzando un TokenRewriteStream e attuare le EnterRule_* metodi parziali per le regole che mi interessa. Anche se questo sembra rendere la modifica del flusso di token abbastanza facile, non c'è abbastanza informazione contestuale per la mia analisi. Sembra che tutto ciò a cui ho accesso sia un flusso piatto di token, che non mi dice abbastanza sull'intera struttura del codice. Ad esempio, per rilevare se la funzione foo è chiamata, semplicemente guardando il primo token non avrebbe funzionato perché sarebbe partita anche il falso:

a.b.foo(); 

per permettermi di fare di più sofisticate analisi del codice, il mio secondo approccio era quello di modificare la grammatica con regole di riscrittura per produrre più di un albero. Ora, il primo blocco di codice di esempio produce questo:

 
Program 
    CallExpression 
     Identifier('foo') 
     ArgumentList 
      ArrayLiteral 
       StringLiteral('a') 
       StringLiteral('b') 
       StringLiteral('c') 

Questo funziona perfettamente per l'analisi del codice. Tuttavia, ora non riesco a riscrivere facilmente il codice. Certo, potrei modificare la struttura ad albero per rappresentare il codice che voglio, ma non posso usarlo per emettere il codice sorgente. Speravo che il token associato a ciascun nodo mi fornisse almeno informazioni sufficienti per sapere dove avrei dovuto apportare le modifiche nel testo originale, ma tutto ciò che ottengo sono indici di token o numeri di riga/colonna. Per usare i numeri di riga e di colonna, dovrei fare una seconda passata scomoda attraverso il codice sorgente.

Sospetto che mi manchi qualcosa nel capire come utilizzare correttamente ANTLR per fare ciò di cui ho bisogno. C'è un modo più corretto per me per risolvere questo problema?

+0

* "C'è un modo più corretto per risolvere questo problema?" *: No, non AFAIK. Analizzi i tuoi input, manipoli e poi li emetti tu stesso. StringTemplate può, come dice Dave, aiutarti in questo. –

risposta

1

Quindi risulta che posso effettivamente utilizzare una grammatica dell'albero di riscrittura e inserire/sostituire i token utilizzando uno TokenRewriteStream. Inoltre, è davvero molto facile da fare. Il mio codice è simile al seguente:

var charStream = new ANTLRInputStream(stream); 
var lexer = new JavaScriptLexer(charStream); 
var tokenStream = new TokenRewriteStream(lexer); 
var parser = new JavaScriptParser(tokenStream); 
var program = parser.program().Tree as Program; 

var dependencies = new List<IModule>(); 

var functionCall = (
    from callExpression in program.Children.OfType<CallExpression>() 
    where callExpression.Children[0].Text == "foo" 
    select callExpression 
).Single(); 
var argList = functionCall.Children[1] as ArgumentList; 
var array = argList.Children[0] as ArrayLiteral; 

tokenStream.InsertAfter(argList.Token.TokenIndex, "'bar', "); 
for (var i = 0; i < array.Children.Count(); i++) 
{ 
    tokenStream.Replace(
     (array.Children[i] as StringLiteral).Token.TokenIndex, 
     i.ToString()); 
} 

var rewrittenCode = tokenStream.ToString(); 
2

Hai guardato la libreria string template. È della stessa persona che ha scritto ANTLR e sono destinati a lavorare insieme. Sembra che sia adatto a fare ciò che stai cercando, ad es. l'output corrisponde alle regole grammaticali come testo formattato.

Here is an article on translation via ANTLR

+0

Speravo di evitare di creare codice per generare un intero programma JavaScript quando tutto ciò che devo fare è iniettare e sostituire parti del codice JavaScript. Ma se ho bisogno di creare un intero motore di output, String Template sembra promettente. Grazie per il link utile all'articolo. – Jacob

6

Che cosa si sta cercando di fare è chiamato program transformation, vale a dire, la generazione automatica di un programma da un altro. Quello che stai facendo "sbagliato" sta assumendo che il parser sia tutto ciò di cui hai bisogno, e scoprendo che non lo è e che devi riempire il vuoto.

Strumenti che fare che questo pozzo hanno parser (per costruire AST), i mezzi per modificare le AST (sia procedurali e modello diretto), e prettyprinters che convertono la (modificato) AST di nuovo nel codice sorgente legale.Sembra che tu stia lottando con il fatto che ANTLR non viene fornito con prettyprinters; questo non fa parte della sua filosofia; ANTLR è un (ottimo) generatore di parser. Altre risposte hanno suggerito di usare i "modelli di stringhe" di ANTLR, che non sono di per sé "prettyprinters", ma possono essere usati per implementare uno, al prezzo di implementarne uno. È più difficile da fare di quanto sembri; vedere la mia risposta SO su compiling an AST back to source code.

Il vero problema qui è il fatto ampiamente, ma falsa presupposto che "se ho un parser, sto bene sul mio modo di costruire analisi programma complesso e strumenti di trasformazione." Vedi il mio saggio su Life After Parsing per una lunga discussione di questo; in sostanza, hai bisogno di molto più strumenti che "solo" un parser per farlo, a meno che tu non voglia ricostruire una frazione significativa dell'infrastruttura da solo invece di andare avanti con il tuo compito. Altre utili caratteristiche dei sistemi di trasformazione pratica del programma includono in genere trasformazioni da sorgente a sorgente, che semplificano notevolmente il problema di trovare e sostituire modelli complessi negli alberi.

Per esempio, se si ha capacità di trasformazione source-to-source (del nostro strumento, il DMS Software Reengineering Toolkit, devi essere in grado di scrivere le parti del codice di esempio viene modificato utilizzando questi DMS trasforma:

 domain ECMAScript. 

     tag replace; -- says this is a special kind of temporary tree 


     rule barize(function_name:IDENTIFIER,list:expression_list,b:body): 
      expression->expression 
     = " \function_name ('[' \list ']') " 
     -> "\function_name(\firstarg\(\function_name\), \replace\(\list\))"; 


     rule replace_unit_list(s:character_literal): 
      expression_list -> expression_list 
      replace(s) -> compute_index_for(s); 

     rule replace_long_list(s:character_list, list:expression_list): 
      expression_list -> expression_list 
      "\replace\(\s\,\list)-> "compute_index_for\(\s\),\list"; 

con regole "meta" esterne procedure "first_arg" (che sa come calcolare "bar" dato l'identificatore "foo" [sto indovinando che vuoi farlo), e "compute_index_for" che ha dato una stringa letterale, sa cosa intero per sostituirlo con.

Le singole regole di riscrittura hanno elenchi di parametri "(....)" in cui gli slot che rappresentano i sottoalberi sono denominato, un lato sinistro che funge da modello da abbinare, e un lato destro che funge da sostituto, entrambi citati solitamente in metaquote " che separa il testo della lingua di riscrittura-regola dal linguaggio di destinazione (ad es. JavaScript) testo. Ci sono un sacco di meta-fughe ** trovate all'interno delle metaquote che indicano un elemento speciale per la lingua delle regole di riscrittura. Generalmente si tratta di nomi di parametri e rappresentano qualsiasi tipo di albero dei nomi rappresentato dal parametro o rappresentano una chiamata di meta procedura esterna (come ad esempio first_arg; si noterà che la sua lista di argomenti (,) è metaquota!), O infine, un " tag "come" replace ", che è un particolare tipo di albero che rappresenta l'intenzione futura di fare più trasformazioni.

Questo particolare insieme di regole funziona sostituendo una chiamata di funzione candidato con la versione ridotta, con l'intento aggiuntivo "sostituisci" per trasformare l'elenco. Le altre due trasformazioni realizzano l'intento trasformando "sostituisci" via elaborando gli elementi della lista uno alla volta, e spingendo la sostituzione più in basso nell'elenco finché non cade definitivamente dalla fine e la sostituzione è fatta. (Questo è l'equivalente trasformazionale di un loop).

L'esempio specifico può variare leggermente in quanto in realtà non erano precisi i dettagli.

Dopo aver applicato queste regole per modificare l'albero analizzato, DMS può quindi semplificare in modo approssimativo il risultato (il comportamento predefinito in alcune configurazioni è "parsing to AST, applicare regole fino all'esaurimento, prettyst AST" perché questo è utile).

È possibile visualizzare un processo completo di "define language", "define rewrite rules", "apply rules and prettyprint" allo (High School) Algebra as a DMS domain.

Altri sistemi di trasformazione del programma includono TXL e Stratego. Immaginiamo DMS come la versione di forza industriale di questi, in cui abbiamo costruito tutta quell'infrastruttura tra cui many standard language parsers and prettyprinters.

+0

Grazie per la risposta dettagliata. Speravo di evitare di dover scrivere un'intera bella stampante (o, in termini di DMS, vorrei una stampante fedeltà). Sto solo volendo iniettare/sostituire porzioni del codice, quindi sarebbe un peccato se dovessi scrivere codice per produrre un intero programma solo per fare questo. – Jacob

+0

Capisco il tuo desiderio di evitare il lavoro: -} Cattive notizie: è piuttosto difficile da fare quando vuoi apportare modifiche al codice. Buone notizie: qualcuno ha fatto tutto il necessario: -} –

Problemi correlati