Detectar lenguaje de programación a partir de un fragmento

115

¿Cuál sería la mejor manera de detectar qué lenguaje de programación se usa en un fragmento de código?

João Matos
fuente
1
Hay prácticamente una infinidad de idiomas por ahí ... ¿quieres detectar ALGUNO de ellos? ¿O solo estamos hablando de los populares?
Spencer Ruport
Solo los más populares (C / C ++, C #, Java, Pascal, Python, VB.NET. PHP, JavaScript y tal vez Haskell).
João Matos
12
Bueno, Haskell no puede ser popular porque nunca he oído hablar de él. ;-)
Stephanie Page
22
Probablemente no sepa mucho sobre lenguajes de programación si no ha oído hablar de Haskell.
Akhorus
4
Existe este servicio en línea que lo hace: algorítmia.com
PetiteProgrammer/…

Respuestas:

99

Creo que el método utilizado en los filtros de spam funcionaría muy bien. Divides el fragmento en palabras. Luego, compara las ocurrencias de estas palabras con fragmentos conocidos y calcula la probabilidad de que este fragmento esté escrito en el idioma X para todos los idiomas que le interesan.

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

Si tiene el mecanismo básico, entonces es muy fácil agregar nuevos idiomas: simplemente entrene el detector con algunos fragmentos en el nuevo idioma (podría alimentarlo con un proyecto de código abierto). De esta manera, aprende que es probable que "Sistema" aparezca en los fragmentos de C # y "pone" en los fragmentos de Ruby.

De hecho, he usado este método para agregar detección de idioma a fragmentos de código para el software del foro. Funcionó el 100% del tiempo, excepto en casos ambiguos:

print "Hello"

Déjame encontrar el código.

No pude encontrar el código, así que hice uno nuevo. Es un poco simplista pero funciona para mis pruebas. Actualmente, si lo alimenta con mucho más código Python que código Ruby, es probable que diga que este código:

def foo
   puts "hi"
end

es código Python (aunque realmente es Ruby). Esto se debe a que Python también tiene una defpalabra clave. Entonces, si ha visto 1000x defen Python y 100x defen Ruby, aún puede decir Python aunque putsyend es Rubí-específica. Puede solucionar esto haciendo un seguimiento de las palabras que se ven por idioma y dividiéndolas por eso en algún lugar (o alimentándolo con cantidades iguales de código en cada idioma).

Espero que te ayude:

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)
Jules
fuente
1
También necesito usarlo en el software del foro. Gracias por el consejo sobre el filtrado bayesiano.
João Matos
12
Hice algo como esto en mi clase de PNL, pero dimos un paso más. No le gusta mirar las frecuencias de una sola palabra, sino pares y triples de palabras. Por ejemplo, "public" puede ser una palabra clave en muchos idiomas, pero "public static void" es más común en C #. Si no se puede encontrar el triple, retrocede a 2 y luego a 1.
mpen
1
También es posible que desee pensar en dónde está dividiendo las palabras. En PHP, las variables comienzan con $, por lo que tal vez no debería dividir en límites de palabras, porque $debería quedarse con la variable. A los operadores les gusta =>y :=deberían estar unidos como un solo token, pero OTH probablemente debería dividirlos {porque siempre se mantienen solos.
mpen
2
Sí. Una forma de evitar la división es usar ngrams: toma cada n subcadena de longitud. Por ejemplo, los 5 gramos de "put foo" son "puts" "uts f", "ts foo" y "s foo". Esta estrategia puede parecer extraña pero funciona mejor de lo que piensas, simplemente no es la forma en que un humano resolvería el problema. Para decidir qué método funciona mejor, tendrá que probar ambos ...
Jules
2
Sin embargo, algunos idiomas tienen muy poca sintaxis. También estoy especulando que los nombres de variables comunes dominarían las palabras clave del idioma. Básicamente, si tiene un fragmento de código C escrito por un húngaro, con nombres de variables y comentarios en húngaro, en sus datos de entrenamiento, entonces es probable que se determine que cualquier otra fuente con húngaro sea "similar".
tripleee
26

Detección de idioma resuelta por otros:

Enfoque de Ohloh: https://github.com/blackducksw/ohcount/

Enfoque de Github: https://github.com/github/linguist

nisc
fuente
4
He examinado ambas soluciones y ninguna hará exactamente lo que se le pidió. Principalmente, miran las extensiones de archivo para determinar el idioma, por lo que no necesariamente pueden examinar un fragmento sin una pista de la extensión.
Hawkee
5
El enfoque de Github ahora también incluye un clasificador bayesiano. Detecta principalmente un candidato de idioma basado en la extensión de archivo, pero cuando una extensión de archivo coincide con varios candidatos (por ejemplo, ".h" -> C, C ++, ObjC), tokenizará la muestra de código de entrada y la clasificará con un conjunto previamente entrenado. de datos. La versión de Github puede verse obligada a escanear el código siempre sin mirar también la extensión.
Benzi
5

Es muy difícil y a veces imposible. ¿De qué idioma es este breve fragmento?

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

(Pista: podría ser cualquiera de varios).

Puede intentar analizar varios idiomas e intentar decidir mediante el análisis de frecuencia de palabras clave. Si ciertos conjuntos de palabras clave ocurren con ciertas frecuencias en un texto, es probable que el lenguaje sea Java, etc. Pero no creo que obtenga nada que sea completamente infalible, ya que podría nombrar, por ejemplo, una variable en C con el mismo nombre como palabra clave en Java, y el análisis de frecuencia será engañado.

Si lo lleva a un nivel superior en complejidad, podría buscar estructuras, si una determinada palabra clave siempre viene después de otra, eso le dará más pistas. Pero también será mucho más difícil de diseñar e implementar.


fuente
26
Bueno, si varios idiomas son posibles, el detector puede dar todos los posibles candidatos.
Steven Haryanto
O puede dar el primero que coincida. Si el caso de uso del mundo real es algo como el resaltado de sintaxis, entonces realmente no haría una diferencia. Lo que significa que cualquiera de los idiomas coincidentes resultaría en resaltar el código correctamente.
jonschlinkert
5

Una alternativa es usar highlight.js , que realiza el resaltado de sintaxis pero usa la tasa de éxito del proceso de resaltado para identificar el idioma. En principio, cualquier base de código de resaltador de sintaxis podría usarse de la misma manera, pero lo bueno de highlight.js es que la detección de idioma se considera una característica y se usa con fines de prueba .

ACTUALIZACIÓN: Intenté esto y no funcionó tan bien. JavaScript comprimido lo confundió por completo, es decir, el tokenizador es sensible a los espacios en blanco. En general, el simple hecho de contar los resultados destacados no parece muy confiable. Un analizador más sólido, o quizás recuentos de secciones incomparables, podrían funcionar mejor.

Andy Jackson
fuente
Los datos de idioma incluidos en highlight.js se limitan a los valores necesarios para resaltar, lo que resulta ser bastante insuficiente para la detección de idiomas (especialmente para pequeñas cantidades de código).
Adam Kennedy
Creo que está bien, consulte con este violín jsfiddle.net/3tgjnz10
sebilasse
4

Primero, trataría de encontrar las teclas específicas de un idioma, por ejemplo

"package, class, implements "=> JAVA
"<?php " => PHP
"include main fopen strcmp stdout "=>C
"cout"=> C++
etc...
Pierre
fuente
3
El problema es que esas palabras clave aún pueden aparecer en cualquier idioma, ya sea como nombres de variables o en cadenas. Eso, y hay mucha superposición en las palabras clave utilizadas. Tendría que hacer algo más que buscar palabras clave.
mpen
2

Dependería del tipo de fragmento que tenga, pero lo ejecutaría a través de una serie de tokenizadores y vería el BNF de qué idioma se encontró como válido.

Sí, ese Jake.
fuente
Todos los idiomas ni siquiera pueden ser descritos por un BNF. Si se le permite redefinir las palabras clave y crear macros, se vuelve mucho más difícil. Además, como estamos hablando de un fragmento, tendrías que hacer una coincidencia parcial contra un BNF, que es más difícil y más propenso a errores.
2

Buen rompecabezas.

Creo que es imposible detectar todos los idiomas. Pero puedes disparar con tokens clave. (ciertas palabras reservadas y combinaciones de caracteres de uso frecuente).

Ben, hay muchos lenguajes con sintaxis similar. Por tanto, depende del tamaño del fragmento.

Toon Krijthe
fuente
1

Prettify es un paquete de Javascript que hace un buen trabajo al detectar lenguajes de programación:

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

Es principalmente un resaltador de sintaxis, pero probablemente haya una manera de extraer la parte de detección con el fin de detectar el idioma de un fragmento.

Hawkee
fuente
1
Tras una inspección más profunda, parece que Prettify en realidad no detecta el idioma, pero resalta de acuerdo con la sintaxis de cada elemento.
Hawkee
1

Necesitaba esto, así que creé el mío. https://github.com/bertyhell/CodeClassifier

Es muy fácil de ampliar agregando un archivo de entrenamiento en la carpeta correcta. Escrito en c #. Pero imagino que el código se convierte fácilmente a cualquier otro idioma.

Berty
fuente
0

No creo que haya una manera fácil de lograrlo. Probablemente generaría listas de símbolos / palabras clave comunes únicas para ciertos idiomas / clases de idiomas (por ejemplo, corchetes para lenguaje de estilo C, las palabras clave Dim y Sub para lenguajes BASIC, la palabra clave def para Python, la palabra clave let para lenguajes funcionales) . A continuación, es posible que pueda utilizar funciones de sintaxis básicas para reducirlo aún más.

Noldorin
fuente
0

Creo que la mayor distinción entre idiomas es su estructura. Entonces mi idea sería mirar ciertos elementos comunes en todos los idiomas y ver en qué se diferencian. Por ejemplo, puede usar expresiones regulares para seleccionar cosas como:

  • definiciones de funciones
  • declaraciones de variables
  • declaraciones de clase
  • comentarios
  • para bucles
  • while bucles
  • imprimir declaraciones

Y tal vez algunas otras cosas que la mayoría de los idiomas deberían tener. Luego usa un sistema de puntos. Otorgue como máximo 1 punto por cada elemento si se encuentra la expresión regular. Obviamente, algunos lenguajes usarán exactamente la misma sintaxis (los bucles for a menudo se escriben comofor(int i=0; i<x; ++i) por lo que varios idiomas podrían obtener un punto por lo mismo, pero al menos está reduciendo la probabilidad de que sea un idioma completamente diferente). Algunos de ellos pueden puntuar 0 en todos los ámbitos (el fragmento no contiene ninguna función, por ejemplo), pero eso está perfectamente bien.

Combine esto con la solución de Jules, y debería funcionar bastante bien. Quizás también busque frecuencias de palabras clave para obtener un punto extra.

mpen
fuente
0

Interesante. Tengo una tarea similar para reconocer texto en diferentes formatos. ¿Propiedades YAML, JSON, XML o Java? Incluso con errores de sintaxis, por ejemplo, debería distinguir JSON de XML con confianza.

Me imagino que cómo modelamos el problema es fundamental. Como dijo Mark, la tokenización de una sola palabra es necesaria, pero probablemente no sea suficiente. Necesitaremos bigramas o incluso trigramas. Pero creo que podemos ir más lejos sabiendo que estamos viendo lenguajes de programación. Noto que casi cualquier lenguaje de programación tiene dos tipos únicos de tokens: símbolos y palabras clave . Los símbolos son relativamente fáciles de reconocer (algunos símbolos pueden ser literales que no forman parte del idioma). Entonces, los bigramas o trigramas de símbolos recogerán estructuras de sintaxis únicas alrededor de los símbolos. Las palabras clave son otro objetivo fácil si el conjunto de formación es lo suficientemente grande y diverso. Una característica útil podría ser bigramas en torno a posibles palabras clave. Otro tipo interesante de token es espacio en blanco.. En realidad, si tokenizamos de la forma habitual mediante espacios en blanco, perderemos esta información. Yo diría que, para analizar lenguajes de programación, mantenemos los tokens de espacios en blanco, ya que pueden contener información útil sobre la estructura de sintaxis.

Finalmente, si elijo un clasificador como bosque aleatorio, rastrearé github y reuniré todo el código fuente público. La mayor parte del archivo de código fuente se puede etiquetar por sufijo de archivo. Para cada archivo, lo dividiré aleatoriamente en líneas vacías en fragmentos de varios tamaños. Luego extraeré las características y entrenaré al clasificador usando los fragmentos etiquetados. Una vez finalizado el entrenamiento, se puede probar la precisión y la recuperación del clasificador.

neurita
fuente
0

La mejor solución que he encontrado es usar la gema lingüista en una aplicación Ruby on Rails. Es una forma específica de hacerlo, pero funciona. Esto fue mencionado anteriormente por @nisc, pero te diré mis pasos exactos para usarlo. (Algunos de los siguientes comandos de línea de comandos son específicos de ubuntu, pero deberían traducirse fácilmente a otros sistemas operativos)

Si tiene alguna aplicación de rails con la que no le importa jugar temporalmente, cree un nuevo archivo para insertar el fragmento de código en cuestión. (Si no tiene rieles instalados, hay una buena guía aquí, aunque para ubuntu recomiendo esto . Luego ejecute rails new <name-your-app-dir>y cd en ese directorio. Todo lo que necesita para ejecutar una aplicación de rieles ya está allí).

Después de tener una aplicación de rieles para usarla, agréguela gem 'github-linguist'a su Gemfile (literalmente, se acaba de llamar Gemfileen el directorio de su aplicación, no ext).

Luego instale ruby-dev ( sudo apt-get install ruby-dev)

Luego instale cmake ( sudo apt-get install cmake)

Ahora puede ejecutar gem install github-linguist(si recibe un error que dice que se requiere icu, hágalo sudo apt-get install libicu-deve intente nuevamente)

(Es posible que tenga que hacer una sudo apt-get updateo sudo apt-get install makeo sudo apt-get install build-essentialsi lo anterior no funcionó)

Ahora todo está configurado. Ahora puede usar esto en cualquier momento que desee verificar fragmentos de código. En un editor de texto, abra el archivo que creó para insertar su fragmento de código (digamos que es, app/test.tplpero si conoce la extensión de su fragmento, utilícelo en lugar de .tpl. Si no conoce la extensión, no use una ). Ahora pegue su fragmento de código en este archivo. Vaya a la línea de comandos y ejecute bundle install(debe estar en el directorio de su aplicación). Luego ejecute linguist app/test.tpl(de manera más general linguist <path-to-code-snippet-file>). Le dirá el tipo, el tipo de mímica y el idioma. Para varios archivos (o para uso general con una aplicación ruby ​​/ rails) puede ejecutar bundle exec linguist --breakdownen el directorio de su aplicación.

Parece mucho trabajo extra, especialmente si aún no tiene rieles, pero en realidad no necesita saber NADA sobre rieles si sigue estos pasos y realmente no he encontrado una mejor manera de detectar el idioma de un archivo / fragmento de código.

StephanieS
fuente
0

Creo que no existe una solución única que pueda identificar en qué idioma se encuentra un fragmento, solo basándose en ese único fragmento. Toma la palabra clave print. Puede aparecer en cualquier número de idiomas, cada uno de los cuales tiene diferentes propósitos y tiene una sintaxis diferente.

Tengo algunos consejos. Actualmente estoy escribiendo un pequeño código para mi sitio web que se puede usar para identificar lenguajes de programación. Como la mayoría de las otras publicaciones, podría haber una gran variedad de lenguajes de programación que simplemente no ha escuchado, no puede explicarlos todos.

Lo que he hecho es que cada idioma se puede identificar mediante una selección de palabras clave. Por ejemplo, Python se puede identificar de varias formas. Probablemente sea más fácil si eliges 'rasgos' que también son ciertamente exclusivos del idioma. Para Python, elijo el rasgo de usar dos puntos para iniciar un conjunto de declaraciones, que creo que es un rasgo bastante único (corríjame si me equivoco).

Si, en mi ejemplo, no puede encontrar dos puntos para iniciar un conjunto de instrucciones, luego pase a otro rasgo posible, digamos que usa la defpalabra clave para definir una función. Ahora bien, esto puede causar algunos problemas, porque Ruby también usa la palabra clave defpara definir una función. La clave para diferenciar los dos (Python y Ruby) es usar varios niveles de filtrado para obtener la mejor coincidencia. Ruby usa la palabra clave endpara terminar una función, mientras que Python no tiene nada para terminar una función, solo un desangrado pero no quieres ir allí. Pero nuevamente, endtambién podría ser Lua, otro lenguaje de programación más para agregar a la mezcla.

Puede ver que los lenguajes de programación simplemente se superponen demasiado. Una palabra clave que podría ser una palabra clave en un idioma podría ser una palabra clave en otro idioma. El uso de una combinación de palabras clave que a menudo van juntas, como Java, public static void main(String[] args)ayuda a eliminar esos problemas.

Como ya he dicho, su mejor oportunidad es buscar palabras clave relativamente únicas o conjuntos de palabras clave para separar una de la otra. Y, si te equivocas, al menos lo intentaste.

William Lee
fuente
0

Configure el codificador aleatorio como

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;
Rakesh
fuente
0

Este sitio parece ser bastante bueno para identificar idiomas, si desea una forma rápida de pegar un fragmento en un formulario web, en lugar de hacerlo mediante programación: http://dpaste.com/

drkvogel
fuente