Определение языка программирования из сниппета


116

Как лучше всего определить, какой язык программирования используется во фрагменте кода?


1
Существует практически бесконечное количество языков ... вы хотите обнаружить ЛЮБОЙ из них? Или мы просто говорим о популярных?
Спенсер Рупорт,

Только популярные (C / C ++, C #, Java, Pascal, Python, VB.NET. PHP, JavaScript и, возможно, Haskell).
Жуан Матос,

12
Ну, Haskell не может быть популярным, потому что я никогда о нем не слышал. ;-)
Стефани Пейдж

22
Вы, вероятно, мало что знаете о языках программирования, если не слышали о Haskell.
Ахорус

4
Для этого существует онлайн-сервис: algorithmia.com/algorithms/PetiteProgrammer/…
Бенни Нойгебауэр,

Ответы:


99

Я думаю, что метод, используемый в спам-фильтрах, будет работать очень хорошо. Вы разбиваете фрагмент на слова. Затем вы сравниваете употребление этих слов с известными фрагментами и вычисляете вероятность того, что этот фрагмент написан на языке X для каждого интересующего вас языка.

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

Если у вас есть базовый механизм, добавлять новые языки очень просто: просто обучите детектор с помощью нескольких фрагментов на новом языке (вы можете скормить ему проект с открытым исходным кодом). Таким образом, он узнает, что «System» может появиться во фрагментах кода C # и «положить» в фрагменты Ruby.

Я действительно использовал этот метод, чтобы добавить определение языка во фрагменты кода для программного обеспечения форума. Он работал 100% времени, за исключением неоднозначных случаев:

print "Hello"

Дай мне найти код.

Я не смог найти код, поэтому сделал новый. Это немного упрощенно, но для моих тестов это работает. В настоящее время, если вы скармливаете ему гораздо больше кода Python, чем кода Ruby, он может сказать, что этот код:

def foo
   puts "hi"
end

- это код Python (хотя на самом деле это Ruby). Это потому, что у Python тоже есть defключевое слово. Поэтому, если он видел 1000x defв Python и 100x defв Ruby, он все равно может говорить Python, хотя putsи endявляется специфичным для Ruby. Вы можете исправить это, отслеживая количество слов, отображаемых для каждого языка, и делясь на них где-нибудь (или вводя равное количество кода на каждом языке).

Надеюсь, это вам поможет:

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
Мне также нужно использовать его в программном обеспечении для форумов. Спасибо за подсказку о байесовской фильтрации.
Жуан Матос,

12
Я делал что-то подобное в своем классе НЛП, но мы пошли дальше. Вам нравится смотреть не на частоту одного слова, а на пары и тройки слов. Например, «public» может быть ключевым словом на многих языках, но «public static void» более характерно для C #. Если тройку не удается найти, вы
переключаетесь

1
Также можно подумать о том, где вы разбиваете слова. В PHP переменные начинаются с $, поэтому, возможно, вам не следует разбивать границы слов, потому что они $должны придерживаться переменной. Операторы любят =>и :=должны быть объединены в один токен, но OTH вам, вероятно, следует разделить на {s, потому что они всегда работают сами по себе.
mpen

2
Ага. Способ вообще избежать разделения - использовать ngrams: вы берете каждую подстроку длины n. Например, 5-граммовое выражение «put foo» - это «put», «uts f», «ts fo» и «s foo». Эта стратегия может показаться странной, но она работает лучше, чем вы думаете, просто человек не может решить проблему. Чтобы решить, какой метод работает лучше, вам придется протестировать оба ...
Жюль

2
Однако в некоторых языках очень мало синтаксиса. Я также предполагаю, что общие имена переменных будут преобладать над ключевыми словами языка. По сути, если у вас есть фрагмент кода C, написанный венгром, с именами переменных и комментариями на венгерском языке в ваших обучающих данных, то любой другой источник с венгерским языком, скорее всего, будет определен как «похожий».
Tripleee

26

Определение языка решено другими:

Подход Олоха: https://github.com/blackducksw/ohcount/

Подход Github: https://github.com/github/linguist


4
Я изучил оба этих решения, и ни один из них не сделает именно то, что просили. В основном они смотрят на расширения файлов, чтобы определить язык, поэтому они не могут обязательно изучить фрагмент без подсказки по расширению.
Hawkee 05

5
Подход Github теперь также включает байесовский классификатор. Он в первую очередь определяет кандидата на язык на основе расширения файла, но когда расширение файла соответствует нескольким кандидатам (например, «.h» -> C, C ++, ObjC), он будет токенизировать образец входного кода и классифицировать его по предварительно обученному набору. данных. Версию Github можно заставить сканировать код всегда, не глядя на расширение.
Benzi

7

Вы можете найти полезный материал здесь: http://alexgorbatchev.com/wiki/SyntaxHighlighter . Алекс потратил много времени, выясняя, как анализировать большое количество разных языков и каковы ключевые элементы синтаксиса.


3
Ссылка мертвая. Похоже, он переехал сюда: alexgorbatchev.com/SyntaxHighlighter
Moonchild

7

Guesslang - возможное решение:

http://guesslang.readthedocs.io/en/latest/index.html

Также есть SourceClassifier:

https://github.com/chrislo/sourceclassifier/tree/master

Я заинтересовался этой проблемой после того, как нашел в статье блога код, который мне не удалось идентифицировать. Добавляем этот ответ, так как этот вопрос был первым поисковым запросом «определить язык программирования».


5

Это очень сложно, а иногда и невозможно. На каком языке написан этот короткий фрагмент?

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

(Подсказка: это может быть любой из нескольких.)

Можно попробовать проанализировать разные языки и попробовать определиться с помощью частотного анализа ключевых слов. Если определенные наборы ключевых слов встречаются с определенной частотой в тексте, вероятно, что это язык Java и т. Д. Но я не думаю, что вы получите что-либо полностью защищенное от дурака, поскольку вы могли бы назвать, например, переменную в C с тем же именем как ключевое слово в Java, и частотный анализ будет обманут.

Если вы поднимете его на ступеньку выше по сложности, вы можете искать структуры, если определенное ключевое слово всегда идет после другого, это даст вам больше подсказок. Но также будет намного сложнее спроектировать и реализовать.


26
Что ж, если возможны несколько языков, детектор может просто выдать всех возможных кандидатов.
Стивен Харьянто

Или он может дать первое совпадение. Если реальный вариант использования - это что-то вроде подсветки синтаксиса, тогда это действительно не имеет значения. Это означает, что любой из совпадающих языков приведет к правильному выделению кода.
jonschlinkert

5

Альтернативой является использование highlight.js , которое выполняет выделение синтаксиса, но использует показатель успешности процесса выделения для идентификации языка. В принципе, любая кодовая база подсветки синтаксиса может использоваться таким же образом, но хорошая вещь в highlight.js заключается в том, что определение языка считается функцией и используется для целей тестирования .

ОБНОВЛЕНИЕ: я пробовал это, и это не сработало. Сжатый JavaScript полностью запутал его, т. Е. Токенизатор чувствителен к пробелам. Как правило, простой подсчет хитов не кажется надежным. Более сильный синтаксический анализатор или, возможно, несовпадающее количество разделов могут работать лучше.


Языковые данные, включенные в highlight.js, ограничены значениями, необходимыми для выделения, что оказывается совершенно недостаточным для определения языка (особенно для небольших объемов кода).
Адам Кеннеди

Думаю, все в порядке, проверьте с этой скрипкой jsfiddle.net/3tgjnz10
sebilasse

4

Во-первых, я бы попытался найти конкретные ключевые слова языка, например

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

3
Проблема в том, что эти ключевые слова по-прежнему могут появляться на любом языке как в виде имен переменных, так и в виде строк. Это, и есть много совпадений в используемых ключевых словах. Вам придется делать больше, чем просто искать ключевые слова.
mpen

2

Это будет зависеть от того, какой у вас тип фрагмента, но я бы пропустил его через серию токенизаторов и посмотрел, для какого языка BNF он подходит.


BNF даже не может описать все языки. Если вам разрешено переопределять ключевые слова и создавать макросы, это становится намного сложнее. Кроме того, поскольку мы говорим о сниппете, вам придется выполнить частичное сопоставление с BNF, что сложнее и более подвержено ошибкам.

2

Хорошая головоломка.

Я думаю, что невозможно обнаружить все языки. Но вы можете запускать ключевые токены. (определенные зарезервированные слова и часто используемые комбинации символов).

Бен, есть много языков с похожим синтаксисом. Так что это зависит от размера сниппета.


1

Prettify - это пакет Javascript, который отлично справляется с определением языков программирования:

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

В основном это подсветка синтаксиса, но, вероятно, есть способ извлечь часть обнаружения для определения языка из сниппета.


1
При дальнейшем рассмотрении кажется, что prettify на самом деле не определяет язык, но выделяет в соответствии с синтаксисом каждого элемента.
Hawkee


1

Мне это было нужно, поэтому я создал свой. https://github.com/bertyhell/CodeClassifier

Его очень легко расширить, добавив обучающий файл в нужную папку. Написано на C #. Но я полагаю, что код легко конвертируется на любой другой язык.


0

Я не думаю, что есть простой способ добиться этого. Я бы, вероятно, создал списки символов / общих ключевых слов, уникальных для определенных языков / классов языков (например, фигурные скобки для языка C-стиля, ключевые слова Dim и Sub для языков BASIC, ключевое слово def для Python, ключевое слово let для функциональных языков) . Тогда вы сможете использовать базовые синтаксические функции, чтобы еще больше сузить его.


0

Я думаю, что самое большое различие между языками - это их структура. Итак, моя идея состояла бы в том, чтобы посмотреть на определенные общие элементы во всех языках и увидеть, чем они отличаются. Например, вы можете использовать регулярные выражения для выделения таких вещей, как:

  • определения функций
  • объявления переменных
  • объявления классов
  • Комментарии
  • для петель
  • пока петли
  • распечатать заявления

И, возможно, еще кое-что, что должно быть в большинстве языков. Тогда используйте балльную систему. Если регулярное выражение найдено, присудите не более 1 балла за каждый элемент. Очевидно, что в некоторых языках будет использоваться один и тот же синтаксис (циклы for часто пишутся так, for(int i=0; i<x; ++i)чтобы каждый из нескольких языков мог получить балл за одно и то же, но, по крайней мере, вы уменьшаете вероятность того, что это будет совершенно другой язык). Некоторые из них могут получить 0 баллов по всем направлениям (например, фрагмент вообще не содержит функции), но это прекрасно.

Объедините это с решением Жюля, и оно должно работать хорошо. Может быть, также поищите частоту ключевых слов для дополнительного балла.


0

Интересный. У меня аналогичная задача по распознаванию текста в разных форматах. Свойства YAML, JSON, XML или Java? Например, даже с синтаксическими ошибками я должен с уверенностью отличать JSON от XML.

Я считаю, что то, как мы моделируем проблему, очень важно. Как сказал Марк, токенизация из одного слова необходима, но, вероятно, недостаточна. Нам понадобятся биграммы или даже триграммы. Но я думаю, что мы можем пойти дальше, зная, что мы смотрим на языки программирования. Я заметил, что почти в любом языке программирования есть два уникальных типа токенов - символы и ключевые слова . Символы относительно легко распознать (некоторые символы могут быть литералами, не являющимися частью языка). Тогда биграммы или триграммы символов подберут уникальные синтаксические структуры вокруг символов. Ключевые слова - еще одна легкая цель, если обучающая выборка достаточно велика и разнообразна. Полезной функцией могут быть биграммы вокруг возможных ключевых слов. Еще один интересный тип токена - пробел.. На самом деле, если мы токенизируем обычным способом через пробел, мы потеряем эту информацию. Я бы сказал, что для анализа языков программирования мы сохраняем маркеры пробелов, поскольку они могут нести полезную информацию о структуре синтаксиса.

Наконец, если я выберу классификатор, например случайный лес, я буду сканировать github и собирать весь общедоступный исходный код. Большая часть файла исходного кода может быть помечена суффиксом файла. Для каждого файла я случайным образом разделю его по пустым строкам на фрагменты разного размера. Затем я извлечу функции и обучу классификатор, используя помеченные фрагменты. После завершения обучения классификатор можно проверить на точность и отзывчивость.


0

Лучшее решение, с которым я столкнулся, - использование гема лингвиста в приложении Ruby on Rails. Это своего рода специфический способ сделать это, но он работает. Это было упомянуто выше @nisc, но я расскажу вам свои точные шаги по его использованию. (Некоторые из следующих команд командной строки специфичны для ubuntu, но должны быть легко переведены на другие ОС)

Если у вас есть какое-либо приложение rails, с которым вы не против временно возиться, создайте в нем новый файл, чтобы вставить рассматриваемый фрагмент кода. (Если у вас нет рельсы установлены там хороший гид здесь , хотя для убунту я рекомендую это . Затем запустите rails new <name-your-app-dir>и перейдите в этот каталог. Все , что вам нужно запустить приложение рельсы уже есть).

После того, как у вас будет приложение rails для его использования, добавьте его gem 'github-linguist'в свой Gemfile (буквально только что вызванный Gemfileв каталоге вашего приложения, без расширения ).

Затем установите ruby-dev ( sudo apt-get install ruby-dev)

Затем установите cmake ( sudo apt-get install cmake)

Теперь вы можете запустить gem install github-linguist(если вы получите сообщение об ошибке, в котором говорится, что требуется icu, выполните sudo apt-get install libicu-devи попробуйте еще раз)

(Возможно, вам придется выполнить sudo apt-get updateили sudo apt-get install makeили, sudo apt-get install build-essentialесли вышеуказанное не сработало)

Теперь все настроено. Теперь вы можете использовать это в любое время, когда захотите проверить фрагменты кода. В текстовом редакторе откройте файл, который вы создали, чтобы вставить фрагмент кода (скажем так, app/test.tplно если вы знаете расширение вашего фрагмента, используйте его вместо .tpl. Если вы не знаете расширение, не используйте его. ). Теперь вставьте фрагмент кода в этот файл. Перейдите в командную строку и запустите bundle install(должно быть в каталоге вашего приложения). Затем бегите linguist app/test.tpl(в более общем плане linguist <path-to-code-snippet-file>). Он сообщит вам тип, тип пантомимы и язык. Для нескольких файлов (или для общего использования с приложением ruby ​​/ rails) вы можете запустить его bundle exec linguist --breakdownв каталоге вашего приложения.

Кажется, это требует много дополнительной работы, особенно если у вас еще нет рельсов, но на самом деле вам не нужно НИЧЕГО знать о рельсах, если вы выполните эти шаги, и я просто действительно не нашел лучшего способа обнаружить рельсы. язык файла / фрагмента кода.


0

Я считаю, что не существует единого решения, которое могло бы определить, на каком языке находится фрагмент, только на основе этого фрагмента. Возьмите ключевое слово print. Он может появляться на любом количестве языков, каждый из которых предназначен для разных целей и имеет разный синтаксис.

У меня есть совет. В настоящее время я пишу небольшой фрагмент кода для своего веб-сайта, который можно использовать для определения языков программирования. Как и в большинстве других сообщений, может быть огромное количество языков программирования, о которых вы просто не слышали, вы не можете учесть их все.

Что я сделал, так это то, что каждый язык можно идентифицировать по набору ключевых слов. Например, Python можно идентифицировать несколькими способами. Вероятно, будет проще, если вы выберете «черты», которые также определенно уникальны для языка. Для Python я выбираю черту использования двоеточия для начала набора операторов, что, на мой взгляд, является довольно уникальной чертой (поправьте меня, если я ошибаюсь).

Если в моем примере вы не можете найти двоеточие для начала набора операторов, перейдите к другой возможной характеристике, скажем, используя defключевое слово для определения функции. Теперь это может вызвать некоторые проблемы, потому что Ruby также использует ключевое слово defдля определения функции. Ключом к различению этих двух (Python и Ruby) является использование различных уровней фильтрации для получения наилучшего соответствия. Ruby использует ключевое слово endдля завершения функции, тогда как Python не имеет ничего для завершения функции, только удаление отступа, но вы не хотите туда идти. Но опять же, это endтакже может быть Lua, еще один язык программирования, который можно добавить.

Вы можете видеть, что языки программирования просто перекрывают друг друга. Одно ключевое слово, которое может быть ключевым словом на одном языке, может оказаться ключевым словом на другом языке. Использование комбинации ключевых слов, которые часто идут вместе, как в Java, public static void main(String[] args)помогает устранить эти проблемы.

Как я уже сказал, ваш лучший шанс - это поиск относительно уникальных ключевых слов или наборов ключевых слов, чтобы отделить одно от другого. И, если вы ошиблись, по крайней мере, вы попробовали.


0

Настройте случайный скремблер, например

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

Этот сайт, кажется, довольно хорошо определяет языки, если вам нужен быстрый способ вставить фрагмент в веб-форму, а не делать это программно: http://dpaste.com/

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.