Rilevamento del linguaggio di programmazione da uno snippet


115

Quale sarebbe il modo migliore per rilevare quale linguaggio di programmazione viene utilizzato in uno snippet di codice?


1
Ci sono praticamente un numero infinito di lingue là fuori ... vuoi rilevarne QUALCUNO? O stiamo solo parlando di quelli popolari?
Spencer Ruport

Solo quelli popolari (C / C ++, C #, Java, Pascal, Python, VB.NET. PHP, JavaScript e forse Haskell).
João Matos

12
Ebbene Haskell non può essere popolare visto che non ne ho mai sentito parlare. ;-)
Stephanie Page

22
Probabilmente non sai molto sui linguaggi di programmazione se non hai sentito parlare di Haskell.
Akhorus

4
C'è questo servizio online che lo fa: algoritmia.com/algorithms/PetiteProgrammer/…
Benny Neugebauer

Risposte:


99

Penso che il metodo utilizzato nei filtri antispam funzionerebbe molto bene. Hai diviso lo snippet in parole. Quindi confronti le occorrenze di queste parole con frammenti noti e calcoli la probabilità che questo frammento sia scritto nella lingua X per ogni lingua che ti interessa.

http://en.wikipedia.org/wiki/Bayesian_spam_filtering

Se hai il meccanismo di base, è molto facile aggiungere nuove lingue: basta addestrare il rilevatore con alcuni frammenti nella nuova lingua (potresti alimentarlo con un progetto open source). In questo modo apprende che è probabile che "System" appaia negli snippet di C # e "put" negli snippet di Ruby.

In realtà ho usato questo metodo per aggiungere il rilevamento della lingua agli snippet di codice per il software del forum. Ha funzionato il 100% delle volte, tranne in casi ambigui:

print "Hello"

Fammi trovare il codice.

Non sono riuscito a trovare il codice, quindi ne ho creato uno nuovo. È un po 'semplicistico ma funziona per i miei test. Attualmente, se gli dai molto più codice Python rispetto al codice Ruby, è probabile che questo codice:

def foo
   puts "hi"
end

è codice Python (sebbene in realtà sia Ruby). Questo perché anche Python ha una defparola chiave. Quindi, se ha visto 1000x defin Python e 100x defin Ruby, allora può ancora dire Python, anche se putse endè Ruby-specifici. Puoi risolvere questo problema tenendo traccia delle parole visualizzate per lingua e dividendole da qualche parte (o inserendole uguali quantità di codice in ciascuna lingua).

Spero che ti aiuti:

class Classifier
  def initialize
    @data = {}
    @totals = Hash.new(1)
  end

  def words(code)
    code.split(/[^a-z]/).reject{|w| w.empty?}
  end

  def train(code,lang)
    @totals[lang] += 1
    @data[lang] ||= Hash.new(1)
    words(code).each {|w| @data[lang][w] += 1 }
  end

  def classify(code)
    ws = words(code)
    @data.keys.max_by do |lang|
      # We really want to multiply here but I use logs 
      # to avoid floating point underflow
      # (adding logs is equivalent to multiplication)
      Math.log(@totals[lang]) +
      ws.map{|w| Math.log(@data[lang][w])}.reduce(:+)
    end
  end
end

# Example usage

c = Classifier.new

# Train from files
c.train(open("code.rb").read, :ruby)
c.train(open("code.py").read, :python)
c.train(open("code.cs").read, :csharp)

# Test it on another file
c.classify(open("code2.py").read) # => :python (hopefully)

1
Devo anche usarlo nel software del forum. Grazie per il suggerimento sul filtro bayesiano.
João Matos,

12
Ho fatto qualcosa di simile nel mio corso di PNL, ma abbiamo fatto un ulteriore passo avanti. Non ti piace guardare le frequenze di una singola parola, ma coppie e triple di parole. Ad esempio, "public" potrebbe essere una parola chiave in molte lingue, ma "public static void" è più comune in C #. Se non si riesce a trovare la tripla, si torna a 2, quindi a 1.
mpen

1
Potrei anche pensare a dove dividi le parole. In PHP, le variabili iniziano con $, quindi forse non dovresti dividere i limiti di parola, perché $dovrebbero restare con la variabile. Agli operatori piace =>e :=dovrebbero essere uniti come un singolo token, ma OTH probabilmente dovresti dividere intorno a {s perché stanno sempre da soli.
mpen

2
Sì. Un modo per evitare del tutto la divisione è usare ngram: prendi ogni n sottostringa di lunghezza. Ad esempio, i 5 grammi di "put foo" sono "put" "uts f", "ts fo" e "s foo". Questa strategia può sembrare strana ma funziona meglio di quanto si pensi, semplicemente non è il modo in cui un essere umano risolverebbe il problema. Per decidere quale metodo funziona meglio dovrai testare entrambi ...
Jules

2
Tuttavia, alcune lingue hanno una sintassi molto ridotta. Sto anche ipotizzando che i nomi di variabili comuni avrebbero dominato sulle parole chiave della lingua. Fondamentalmente, se hai un pezzo di codice C scritto da un ungherese, con nomi di variabili e commenti in ungherese, nei tuoi dati di allenamento, allora è probabile che qualsiasi altra fonte con un ungherese sia "simile".
tripla

26

Rilevamento della lingua risolto da altri:

L'approccio di Ohloh: https://github.com/blackducksw/ohcount/

L'approccio di Github: https://github.com/github/linguist


4
Ho esaminato entrambe queste soluzioni e nessuna delle due farà esattamente ciò che è stato chiesto. Esaminano principalmente le estensioni dei file per determinare la lingua, quindi non possono necessariamente esaminare uno snippet senza un indizio dall'estensione.
Hawkee

5
L'approccio di Github ora include anche un classificatore bayesiano. Rileva principalmente un candidato di lingua in base all'estensione del file, ma quando un'estensione di file corrisponde a più candidati (ad es. ".H" -> C, C ++, ObjC), tokenizzerà il codice di input di esempio e lo classificherà rispetto a un set pre-addestrato di dati. La versione Github può essere forzata a scansionare il codice sempre senza guardare anche l'estensione.
Benzi



5

È molto difficile ea volte impossibile. Da quale lingua proviene questo breve snippet?

int i = 5;
int k = 0;
for (int j = 100 ; j > i ; i++) {
    j = j + 1000 / i;
    k = k + i * j;
}

(Suggerimento: potrebbe essere uno qualsiasi dei tanti.)

Puoi provare ad analizzare varie lingue e provare a decidere utilizzando l'analisi della frequenza delle parole chiave. Se alcuni gruppi di parole chiave si verificano con determinate frequenze in un testo è probabile che la lingua sia Java ecc. Ma non penso che otterrai nulla che sia completamente a prova di errore, poiché potresti nominare ad esempio una variabile in C con lo stesso nome come parola chiave in Java e l'analisi della frequenza verrà ingannata.

Se aumenti la complessità potresti cercare strutture, se una certa parola chiave viene sempre dopo un'altra, questo ti darà più indizi. Ma sarà anche molto più difficile da progettare e implementare.


26
Bene, se sono possibili più lingue, il rilevatore può semplicemente fornire tutti i possibili candidati.
Steven Haryanto

Oppure può dare il primo che corrisponde. Se il caso d'uso nel mondo reale fosse qualcosa come l'evidenziazione della sintassi, allora non farebbe davvero la differenza. Significa che una qualsiasi delle lingue corrispondenti comporterebbe l'evidenziazione del codice correttamente.
jonschlinkert

5

Un'alternativa è usare highlight.js , che esegue l'evidenziazione della sintassi ma utilizza la percentuale di successo del processo di evidenziazione per identificare la lingua. In linea di principio, qualsiasi codebase per l'evidenziatore di sintassi potrebbe essere utilizzato allo stesso modo, ma la cosa bella di highlight.js è che il rilevamento della lingua è considerato una funzionalità e viene utilizzato a scopo di test .

AGGIORNAMENTO: l' ho provato e non ha funzionato molto bene. JavaScript compresso lo ha completamente confuso, cioè il tokenizer è sensibile agli spazi. In generale, il solo conteggio dei risultati in evidenza non sembra molto affidabile. Un parser più forte, o forse un conteggio delle sezioni senza eguali, potrebbe funzionare meglio.


I dati sulla lingua inclusi in highlight.js sono limitati ai valori necessari per l'evidenziazione, che risulta essere abbastanza insufficiente per il rilevamento della lingua (specialmente per piccole quantità di codice).
Adam Kennedy

Penso che vada
sebilasse

4

In primo luogo, proverei a trovare i keywork specifici di una lingua, ad es

"package, class, implements "=> JAVA
"<?php " => PHP
"include main fopen strcmp stdout "=>C
"cout"=> C++
etc...

3
Il problema è che quelle parole chiave possono ancora apparire in qualsiasi lingua, sia come nomi di variabili che in stringhe. Questo e c'è molta sovrapposizione nelle parole chiave utilizzate. Dovresti fare di più che cercare solo parole chiave.
mpen

2

Dipenderà dal tipo di frammento che hai, ma lo eseguirò attraverso una serie di tokenizzatori e vedrei quale BNF della lingua è risultato valido.


Tutte le lingue non possono nemmeno essere descritte da un BNF. Se ti è consentito ridefinire le parole chiave e creare macro, diventa molto più difficile. Anche se stiamo parlando di uno snippet, dovresti fare un match parziale contro un BNF, che è più difficile e più soggetto a errori.

2

Bel puzzle.

Penso che sia impossibile rilevare tutte le lingue. Ma potresti attivare i token chiave. (alcune parole riservate e combinazioni di caratteri usate spesso).

Ben ci sono molte lingue con sintassi simile. Quindi dipende dalla dimensione dello snippet.


1

Prettify è un pacchetto Javascript che fa un buon lavoro nel rilevare i linguaggi di programmazione:

http://code.google.com/p/google-code-prettify/

È principalmente un evidenziatore della sintassi, ma probabilmente esiste un modo per estrarre la parte di rilevamento allo scopo di rilevare la lingua da uno snippet.


1
Ad un'ulteriore ispezione sembra che prettify non rilevi effettivamente la lingua, ma evidenzia in base alla sintassi di ogni elemento.
Hawkee


1

Avevo bisogno di questo così ho creato il mio. https://github.com/bertyhell/CodeClassifier

È molto facilmente estendibile aggiungendo un file di allenamento nella cartella corretta. Scritto in c #. Ma immagino che il codice possa essere facilmente convertito in qualsiasi altra lingua.


0

Non penserei che ci sarebbe un modo semplice per ottenere questo risultato. Probabilmente genererei elenchi di simboli / parole chiave comuni univoche per determinati linguaggi / classi di linguaggi (es. Parentesi graffe per il linguaggio in stile C, le parole chiave Dim e Sub per i linguaggi BASIC, la parola chiave def per Python, la parola chiave let per i linguaggi funzionali) . Potresti quindi essere in grado di utilizzare le funzionalità di sintassi di base per restringerlo ulteriormente.


0

Penso che la più grande distinzione tra le lingue sia la sua struttura. Quindi la mia idea sarebbe quella di guardare alcuni elementi comuni in tutte le lingue e vedere come differiscono. Ad esempio, potresti usare le espressioni regolari per selezionare cose come:

  • definizioni di funzioni
  • dichiarazioni di variabili
  • dichiarazioni di classe
  • Commenti
  • per i loop
  • while loop
  • dichiarazioni di stampa

E forse alcune altre cose che la maggior parte delle lingue dovrebbe avere. Quindi usa un sistema a punti. Assegna al massimo 1 punto per ogni elemento se viene trovata la regex. Ovviamente, alcune lingue useranno la stessa identica sintassi (i cicli for sono spesso scritti in for(int i=0; i<x; ++i)modo che più lingue possano ottenere un punto per la stessa cosa, ma almeno stai riducendo la probabilità che sia una lingua completamente diversa). Alcuni di loro potrebbero segnare 0 su tutta la linea (lo snippet non contiene affatto una funzione, per esempio) ma va benissimo.

Combina questo con la soluzione di Jules e dovrebbe funzionare abbastanza bene. Forse cerca anche le frequenze delle parole chiave per un punto in più.


0

Interessante. Ho un compito simile per riconoscere il testo in diversi formati. Proprietà YAML, JSON, XML o Java? Anche con errori di sintassi, ad esempio, dovrei distinguere JSON da XML con sicurezza.

Immagino che il modo in cui modelliamo il problema sia critico. Come ha detto Mark, la tokenizzazione di una sola parola è necessaria ma probabilmente non sufficiente. Avremo bisogno di bigrammi, o anche di trigrammi. Ma penso che possiamo andare oltre sapendo che stiamo guardando ai linguaggi di programmazione. Ho notato che quasi tutti i linguaggi di programmazione hanno due tipi unici di token: simboli e parole chiave . I simboli sono relativamente facili (alcuni simboli potrebbero essere letterali non parte della lingua) da riconoscere. Quindi bigrammi o trigrammi di simboli raccoglieranno strutture di sintassi uniche attorno ai simboli. Le parole chiave sono un altro obiettivo facile se il set di formazione è abbastanza ampio e diversificato. Una caratteristica utile potrebbe essere bigram intorno a possibili parole chiave. Un altro tipo interessante di token è lo spazio bianco. In realtà, se tokenizziamo nel solito modo con spazi bianchi, perderemo questa informazione. Direi che, per analizzare i linguaggi di programmazione, teniamo i token di spazi vuoti in quanto ciò potrebbe contenere informazioni utili sulla struttura della sintassi.

Infine, se scelgo un classificatore come la foresta casuale, eseguirò la scansione di github e raccoglierò tutto il codice sorgente pubblico. La maggior parte del file del codice sorgente può essere etichettato in base al suffisso del file. Per ogni file, lo dividerò casualmente su righe vuote in frammenti di varie dimensioni. Quindi estrarò le funzionalità e addestrerò il classificatore utilizzando gli snippet etichettati. Al termine dell'addestramento, è possibile testare la precisione e il richiamo del classificatore.


0

La migliore soluzione che ho trovato è usare la gemma del linguista in un'app Ruby on Rails. È un modo specifico per farlo, ma funziona. Questo è stato menzionato sopra da @nisc ma ti dirò i miei passi esatti per usarlo. (Alcuni dei seguenti comandi della riga di comando sono specifici di Ubuntu ma dovrebbero essere facilmente tradotti in altri sistemi operativi)

Se hai un'app rails con cui non ti dispiace temporaneamente scherzare, crea un nuovo file al suo interno per inserire lo snippet di codice in questione. (Se non si dispone di rotaie installato c'è una buona guida qui anche se per Ubuntu vi consiglio questo . Quindi eseguire rails new <name-your-app-dir>e cd in quella directory. Tutto il necessario per eseguire un'applicazione Rails è già lì).

Dopo che hai un'app rails con cui usarlo, aggiungilo gem 'github-linguist'al tuo Gemfile (letteralmente chiamato solo Gemfilenella directory dell'app, senza ext).

Quindi installa ruby-dev ( sudo apt-get install ruby-dev)

Quindi installa cmake ( sudo apt-get install cmake)

Ora puoi eseguire gem install github-linguist(se ricevi un errore che dice che è necessaria la terapia intensiva, fallo sudo apt-get install libicu-deve riprova)

(Potrebbe essere necessario eseguire un sudo apt-get updateo sudo apt-get install makeo sudo apt-get install build-essentialse quanto sopra non ha funzionato)

Adesso è tutto pronto. Ora puoi utilizzarlo ogni volta che desideri controllare gli snippet di codice. In un editor di testo, apri il file che hai creato per inserire lo snippet di codice (diciamo solo che lo è, app/test.tplma se conosci l'estensione del tuo snippet, usalo invece di .tpl. Se non conosci l'estensione, non usarne uno ). Ora incolla lo snippet di codice in questo file. Vai alla riga di comando ed esegui bundle install(deve essere nella directory dell'applicazione). Quindi esegui linguist app/test.tpl(più in generale linguist <path-to-code-snippet-file>). Ti dirà il tipo, il tipo MIME e la lingua. Per più file (o per uso generale con un'app ruby ​​/ rails) puoi eseguire bundle exec linguist --breakdownnella directory dell'applicazione.

Sembra che ci sia un sacco di lavoro extra, soprattutto se non hai già i binari, ma in realtà non hai bisogno di sapere NULLA sui binari se segui questi passaggi e non ho davvero trovato un modo migliore per rilevare il lingua di un file / frammento di codice.


0

Credo che non ci sia un'unica soluzione che possa identificare in quale lingua sia uno snippet, basandosi solo su quel singolo snippet. Prendi la parola chiave print. Potrebbe apparire in un numero qualsiasi di lingue, ognuna delle quali ha scopi diversi e ha una sintassi diversa.

Ho qualche consiglio. Attualmente sto scrivendo un piccolo pezzo di codice per il mio sito web che può essere utilizzato per identificare i linguaggi di programmazione. Come la maggior parte degli altri post, potrebbe esserci una vasta gamma di linguaggi di programmazione che semplicemente non hai sentito, non puoi spiegarli tutti.

Quello che ho fatto è che ogni lingua può essere identificata da una selezione di parole chiave. Ad esempio, Python potrebbe essere identificato in diversi modi. Probabilmente è più facile se scegli "tratti" che sono anche certamente unici per la lingua. Per Python, scelgo la caratteristica di usare i due punti per iniziare una serie di istruzioni, che credo sia una caratteristica abbastanza unica (correggetemi se sbaglio).

Se, nel mio esempio, non riesci a trovare i due punti per avviare un set di istruzioni, passa a un altro tratto possibile, diciamo utilizzando la defparola chiave per definire una funzione. Ora questo può causare alcuni problemi, perché Ruby usa anche la parola chiave defper definire una funzione. La chiave per distinguere i due (Python e Ruby) è usare vari livelli di filtraggio per ottenere la migliore corrispondenza. Ruby usa la parola chiave endper finire una funzione, mentre Python non ha nulla per finire una funzione, solo un de-indent ma tu non vuoi andare lì. Ma ancora una volta, endpotrebbe anche essere Lua, un altro linguaggio di programmazione da aggiungere al mix.

Puoi vedere che i linguaggi di programmazione si sovrappongono semplicemente troppo. Una parola chiave che potrebbe essere una parola chiave in una lingua potrebbe essere una parola chiave in un'altra lingua. L'uso di una combinazione di parole chiave che spesso vanno insieme, come quelle di Java, public static void main(String[] args)aiuta a eliminare questi problemi.

Come ho già detto, la tua migliore possibilità è cercare parole chiave o set di parole chiave relativamente uniche per separare l'una dall'altra. E, se sbagli, almeno ci hai provato.


0

Imposta lo scrambler casuale come

matrix S = matrix(GF(2),k,[random()<0.5for _ in range(k^2)]); while (rank(S) < k) : S[floor(k*random()),floor(k*random())] +=1;

0

Questo sito sembra essere abbastanza bravo nell'identificare le lingue, se desideri un modo rapido per incollare uno snippet in un modulo web, piuttosto che farlo in modo programmatico: http://dpaste.com/

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.