Wykrywanie języka programowania z fragmentu kodu

115

Jaki byłby najlepszy sposób na wykrycie, jaki język programowania jest używany we fragmencie kodu?

João Matos
źródło
1
Istnieje praktycznie nieskończona liczba języków ... czy chcesz wykryć JAKIEKOLWIEK z nich? A może rozmawiamy tylko o popularnych?
Spencer Ruport
Tylko te popularne (C / C ++, C #, Java, Pascal, Python, VB.NET. PHP, JavaScript i może Haskell).
João Matos
12
Cóż, Haskell nie może być popularny, ponieważ nigdy o nim nie słyszałem. ;-)
Stephanie Page
22
Prawdopodobnie niewiele wiesz o językach programowania, jeśli nie słyszałeś o Haskellu.
Akhorus
4
Jest taka usługa online, która to robi: algorytmia.com/algorithms/PetiteProgrammer/ ...
Benny Neugebauer

Odpowiedzi:

99

Myślę, że metoda zastosowana w filtrach antyspamowych działałaby bardzo dobrze. Podzieliłeś fragment na słowa. Następnie porównujesz występowanie tych słów ze znanymi fragmentami i obliczasz prawdopodobieństwo, że ten fragment jest napisany w języku X dla każdego języka, który Cię interesuje.

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

Jeśli masz podstawowy mechanizm, bardzo łatwo jest dodać nowe języki: po prostu wytrenuj detektor z kilkoma fragmentami nowego języka (możesz przesłać mu projekt open source). W ten sposób uczy się, że „System” prawdopodobnie pojawi się we fragmentach kodu C #, a „puts” we fragmentach Rubiego.

W rzeczywistości użyłem tej metody, aby dodać wykrywanie języka do fragmentów kodu oprogramowania forum. Działało w 100%, z wyjątkiem niejednoznacznych przypadków:

print "Hello"

Pozwól mi znaleźć kod.

Nie mogłem znaleźć kodu, więc stworzyłem nowy. To trochę uproszczone, ale działa w moich testach. Obecnie, jeśli podasz mu znacznie więcej kodu Pythona niż kodu Ruby, prawdopodobnie powiesz, że ten kod:

def foo
   puts "hi"
end

jest kodem Pythona (chociaż tak naprawdę jest to Ruby). Dzieje się tak, ponieważ Python również ma defsłowo kluczowe. Więc jeśli zobaczył 1000x defw Pythonie i 100x defw Rubim, może nadal mówić Python, mimo że putsi endjest specyficzny dla Rubiego. Możesz to naprawić, śledząc słowa widoczne w każdym języku i dzieląc je gdzieś (lub wprowadzając równe ilości kodu w każdym języku).

Mam nadzieję, że Ci to pomoże:

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
źródło
1
Muszę go również użyć w oprogramowaniu forum. Dzięki za wskazówkę dotyczącą filtrowania Bayesa.
João Matos
12
Zrobiłem coś takiego w mojej klasie NLP, ale poszliśmy o krok dalej. Nie lubisz patrzeć na częstotliwości pojedynczego słowa, ale na pary i tróje słów. Na przykład „public” może być słowem kluczowym w wielu językach, ale „public static void” jest bardziej powszechne w C #. Jeśli nie można znaleźć potrójnego,
cofasz
1
Warto też pomyśleć o tym, gdzie dzielisz słowa. W PHP zmienne zaczynają się od $, więc może nie powinieneś dzielić się na granice słów, ponieważ $powinny one trzymać się zmiennej. Operatorzy lubią =>i :=powinni być trzymani razem jako pojedynczy token, ale OTH prawdopodobnie powinieneś podzielić się wokół {s, ponieważ zawsze stoją samodzielnie.
mpen
2
Tak. Sposobem na całkowite uniknięcie dzielenia jest użycie ngramów: bierzesz każdy n podciąg. Na przykład 5-gramowe „puts foo” to „puts”, „uts f”, „ts fo” i „s foo”. Ta strategia może wydawać się dziwna, ale działa lepiej niż myślisz, po prostu nie jest to sposób, w jaki człowiek mógłby rozwiązać problem. Aby zdecydować, która metoda działa lepiej, musisz przetestować obie ...
Jules
2
Jednak niektóre języki mają bardzo małą składnię. Spekuluję również, że popularne nazwy zmiennych będą dominować nad słowami kluczowymi języka. Zasadniczo, jeśli masz fragment kodu C napisany przez Węgra, z nazwami zmiennych i komentarzami w języku węgierskim, w danych treningowych, to każde inne źródło zawierające węgierski zostanie prawdopodobnie uznane za „podobne”.
tripleee
26

Wykrywanie języka rozwiązane przez innych:

Podejście Ohloh: https://github.com/blackducksw/ohcount/

Podejście Github: https://github.com/github/linguist

nisc
źródło
4
Przeanalizowałem oba te rozwiązania i żadne z nich nie zrobi dokładnie tego, o co proszono. Głównie patrzą na rozszerzenia plików, aby określić język, więc niekoniecznie muszą badać fragment bez wskazówki z rozszerzenia.
Hawkee,
5
Podejście Githuba obejmuje teraz również klasyfikator Bayesa. Wykrywa przede wszystkim kandydata na język na podstawie rozszerzenia pliku, ale gdy rozszerzenie pliku pasuje do wielu kandydatów (np. „.H” -> C, C ++, ObjC), tokenizuje przykładowy kod wejściowy i klasyfikuje go według wstępnie wytrenowanego zestawu danych. Wersja Github może być zmuszona do skanowania kodu zawsze bez patrzenia na rozszerzenie.
Benzi
5

To bardzo trudne, a czasem niemożliwe. Z jakiego języka pochodzi ten krótki fragment?

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

(Podpowiedź: może to być jeden z kilku.)

Możesz spróbować przeanalizować różne języki i spróbować zdecydować, korzystając z analizy częstotliwości słów kluczowych. Jeśli określone zestawy słów kluczowych występują z określoną częstotliwością w tekście, prawdopodobnie jest to język Java itp. Ale nie sądzę, abyś uzyskał coś, co jest całkowicie głupie, ponieważ możesz na przykład nazwać zmienną w C o tej samej nazwie jako słowo kluczowe w Javie, a analiza częstotliwości zostanie oszukana.

Jeśli podejmiesz wyższy poziom złożoności, możesz poszukać struktur, jeśli określone słowo kluczowe zawsze występuje po innym, dostaniesz więcej wskazówek. Ale będzie też znacznie trudniej zaprojektować i wdrożyć.


źródło
26
Cóż, jeśli możliwych jest kilka języków, detektor może po prostu podać wszystkich możliwych kandydatów.
Steven Haryanto,
Lub może dać pierwszy, który pasuje. Jeśli przykład użycia w świecie rzeczywistym jest czymś w rodzaju podświetlania składni, to naprawdę nie miałoby to znaczenia. Oznacza to, że dowolny z pasujących języków spowodowałby poprawne wyróżnienie kodu.
jonschlinkert
5

Alternatywą jest użycie highlight.js , która wykonuje podświetlanie składni, ale używa współczynnika powodzenia procesu podświetlania do identyfikacji języka. W zasadzie każda baza kodu podświetlająca składnię może być używana w ten sam sposób, ale fajną rzeczą w highlight.js jest to, że wykrywanie języka jest uważane za funkcję i jest używane do celów testowych .

AKTUALIZACJA: próbowałem tego i nie działało tak dobrze. Skompresowany JavaScript całkowicie go zdezorientował, tj. Tokenizer jest wrażliwy na białe znaki. Generalnie samo liczenie trafień w najciekawsze miejsca nie wydaje się zbyt wiarygodne. Silniejszy parser lub może niezrównana liczba sekcji może działać lepiej.

Andy Jackson
źródło
Dane językowe zawarte w highlight.js są ograniczone do wartości potrzebnych do podświetlenia, co okazuje się być dość niewystarczające do wykrywania języka (szczególnie w przypadku małych ilości kodu).
Adam Kennedy
Myślę, że jest w porządku, sprawdź z tym skrzypcem jsfiddle.net/3tgjnz10
sebilasse
4

Najpierw spróbuję znaleźć konkretne słowa kluczowe języka, np

"package, class, implements "=> JAVA
"<?php " => PHP
"include main fopen strcmp stdout "=>C
"cout"=> C++
etc...
Pierre
źródło
3
Problem polega na tym, że te słowa kluczowe mogą nadal występować w dowolnym języku, jako nazwy zmiennych lub w postaci ciągów. To i wiele się pokrywa w używanych słowach kluczowych. Musiałbyś zrobić więcej niż tylko szukać słów kluczowych.
mpen
2

Zależałoby to od typu posiadanego fragmentu kodu, ale przepuściłbym go przez serię tokenizatorów i sprawdziłbym, w przypadku którego BNF języka jest ważny.

Tak - ten Jake.
źródło
Wszystkie języki nie mogą być nawet opisane przez BNF. Jeśli możesz przedefiniować słowa kluczowe i tworzyć makra, jest to znacznie trudniejsze. Alså, ponieważ mówimy o fragmencie, musiałbyś wykonać częściowe dopasowanie do BNF, co jest trudniejsze i bardziej podatne na błędy.
2

Niezła łamigłówka.

Myślę, że niemożliwe jest wykrycie wszystkich języków. Ale możesz uruchomić na kluczowych tokenach. (niektóre zastrzeżone słowa i często używane kombinacje znaków).

Ben, istnieje wiele języków o podobnej składni. Więc to zależy od rozmiaru fragmentu.

Toon Krijthe
źródło
1

Prettify to pakiet Javascript, który dobrze wykrywa języki programowania:

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

Jest to głównie narzędzie do podświetlania składni, ale prawdopodobnie istnieje sposób na wyodrębnienie części wykrywającej w celu wykrycia języka z fragmentu.

Hawkee
źródło
1
Po dalszej kontroli wydaje się, że upiększanie w rzeczywistości nie wykrywa języka, ale wyróżnia się zgodnie ze składnią każdego elementu.
Hawkee,
1

Potrzebowałem tego, więc stworzyłem własny. https://github.com/bertyhell/CodeClassifier

Można go bardzo łatwo rozszerzyć, dodając plik szkoleniowy w odpowiednim folderze. Napisane w C #. Ale wyobrażam sobie, że kod można łatwo przekonwertować na dowolny inny język.

Berty
źródło
0

Nie sądzę, że można to osiągnąć w łatwy sposób. Prawdopodobnie wygenerowałbym listy symboli / wspólnych słów kluczowych unikalnych dla niektórych języków / klas języków (np. Nawiasy klamrowe dla języka w stylu C, słowa kluczowe Dim i Sub dla języków BASIC, słowo kluczowe def dla Pythona, słowo kluczowe let dla języków funkcjonalnych) . Wtedy możesz użyć podstawowych funkcji składni, aby jeszcze bardziej zawęzić.

Noldorin
źródło
0

Myślę, że największą różnicą między językami jest ich struktura. Więc moim pomysłem byłoby przyjrzenie się pewnym wspólnym elementom we wszystkich językach i zobaczenie, jak się różnią. Na przykład możesz użyć wyrażeń regularnych, aby wybrać takie rzeczy, jak:

  • definicje funkcji
  • deklaracje zmiennych
  • deklaracje klas
  • komentarze
  • na pętle
  • pętle while
  • drukowanie oświadczeń

I może kilka innych rzeczy, które powinna mieć większość języków. Następnie użyj systemu punktowego. Przyznaj maksymalnie 1 punkt za każdy element, jeśli zostanie znalezione wyrażenie regularne. Oczywiście niektóre języki będą używać dokładnie tej samej składni (ponieważ pętle są często pisane w taki for(int i=0; i<x; ++i)sposób, że wiele języków może dać punkt za tę samą rzecz, ale przynajmniej zmniejsza się prawdopodobieństwo, że jest to zupełnie inny język). Niektóre z nich mogą uzyskać 0 na całej tablicy (na przykład fragment w ogóle nie zawiera funkcji), ale to w porządku.

Połącz to z rozwiązaniem Julesa i powinno działać całkiem dobrze. Może też poszukaj częstotliwości słów kluczowych dla dodatkowego punktu.

mpen
źródło
0

Ciekawy. Mam podobne zadanie rozpoznawania tekstu w różnych formatach. Właściwości YAML, JSON, XML lub Java? Na przykład nawet w przypadku błędów składniowych powinienem z pewnością odróżnić JSON od XML.

Uważam, że sposób modelowania problemu jest krytyczny. Jak powiedział Mark, tokenizacja jednowyrazowa jest konieczna, ale prawdopodobnie nie wystarczy. Będziemy potrzebować bigramów, a nawet trygramów. Ale myślę, że możemy pójść dalej, wiedząc, że patrzymy na języki programowania. Zauważyłem, że prawie każdy język programowania ma dwa unikalne typy tokenów - symbole i słowa kluczowe . Symbole są stosunkowo łatwe do rozpoznania (niektóre symbole mogą być literałami, które nie są częścią języka). Wtedy bigramy lub trygramy symboli przejmą unikalne struktury składniowe wokół symboli. Słowa kluczowe to kolejny łatwy cel, jeśli zbiór treningowy jest wystarczająco duży i zróżnicowany. Przydatną funkcją mogą być duże ramki wokół możliwych słów kluczowych. Innym interesującym typem tokena są białe znaki. W rzeczywistości, jeśli tokenizujemy w zwykły sposób za pomocą białych znaków, utracimy te informacje. Powiedziałbym, że do analizowania języków programowania zachowujemy białe znaki, ponieważ mogą one zawierać przydatne informacje o strukturze składni.

Wreszcie, jeśli wybiorę klasyfikator, taki jak losowy las, przeszukam Github i zgromadzę cały publiczny kod źródłowy. Większość plików z kodem źródłowym można oznaczyć za pomocą sufiksu pliku. Dla każdego pliku losowo podzielę go w pustych wierszach na fragmenty o różnych rozmiarach. Następnie wyodrębnię cechy i nauczę klasyfikatora za pomocą oznaczonych fragmentów. Po zakończeniu treningu klasyfikator można przetestować pod kątem dokładności i przypominania.

neuryt
źródło
0

Najlepszym rozwiązaniem, z jakim się spotkałem, jest użycie klejnotu lingwistycznego w aplikacji Ruby on Rails. To trochę specyficzny sposób, ale działa. Wspomniał o tym powyżej @nisc, ale powiem ci dokładnie, jak z niego korzystać. (Niektóre z poniższych poleceń wiersza poleceń są specyficzne dla systemu Ubuntu, ale powinny być łatwo przetłumaczone na inne systemy operacyjne)

Jeśli masz jakąkolwiek aplikację railsową, w której nie masz nic przeciwko tymczasowemu manipulowaniu, utwórz w niej nowy plik, aby wstawić odpowiedni fragment kodu. (Jeśli nie masz zainstalowanych railsów, jest tutaj dobry przewodnik , chociaż dla Ubuntu polecam to . Następnie uruchom rails new <name-your-app-dir>i cd do tego katalogu. Wszystko, czego potrzebujesz do uruchomienia aplikacji railsowej, jest już dostępne).

Gdy masz już aplikację gem 'github-linguist'railsową , z której możesz tego korzystać, dodaj do swojego Gemfile (dosłownie wywołane Gemfilew katalogu aplikacji, bez rozszerzenia).

Następnie zainstaluj ruby-dev ( sudo apt-get install ruby-dev)

Następnie zainstaluj cmake ( sudo apt-get install cmake)

Teraz możesz uruchomić gem install github-linguist(jeśli pojawi się błąd, który mówi, że wymagane jest icu, zrób sudo apt-get install libicu-devi spróbuj ponownie)

(Może być konieczne wykonanie sudo apt-get updatelub sudo apt-get install makelub sudo apt-get install build-essentialjeśli powyższe nie zadziałało)

Teraz wszystko jest gotowe. Możesz teraz użyć tego w dowolnym momencie, gdy chcesz sprawdzić fragmenty kodu. W edytorze tekstu otwórz plik, który utworzyłeś, aby wstawić fragment kodu (powiedzmy, że jest to, app/test.tplale jeśli znasz rozszerzenie fragmentu, użyj go zamiast .tpl. Jeśli nie znasz rozszerzenia, nie używaj go ). Teraz wklej swój fragment kodu w tym pliku. Przejdź do wiersza poleceń i uruchom bundle install(musi znajdować się w katalogu aplikacji). Następnie uruchom linguist app/test.tpl(bardziej ogólnie linguist <path-to-code-snippet-file>). Podaje typ, typ MIME i język. W przypadku wielu plików (lub do ogólnego użytku z aplikacją ruby ​​/ rails) możesz uruchomić bundle exec linguist --breakdownw katalogu swojej aplikacji.

Wydaje się, że to dużo dodatkowej pracy, zwłaszcza jeśli nie masz jeszcze szyn, ale tak naprawdę nie musisz niczego wiedzieć o szynach, jeśli wykonasz te kroki, a ja naprawdę nie znalazłem lepszego sposobu na wykrycie język pliku / fragmentu kodu.

StephanieS
źródło
0

Uważam, że nie ma jednego rozwiązania, które mogłoby zidentyfikować język, w którym znajduje się fragment, tylko na podstawie tego pojedynczego fragmentu. Weź słowo kluczowe print. Może pojawić się w dowolnej liczbie języków, z których każdy służy do innych celów i mieć inną składnię.

Mam kilka rad. Obecnie piszę mały fragment kodu dla mojej witryny internetowej, którego można użyć do identyfikacji języków programowania. Podobnie jak większość innych postów, może istnieć ogromna liczba języków programowania, których po prostu nie słyszałeś, nie możesz ich wszystkich wyjaśnić.

Zrobiłem to, że każdy język można zidentyfikować za pomocą wybranych słów kluczowych. Na przykład Python można zidentyfikować na wiele sposobów. Prawdopodobnie jest to łatwiejsze, jeśli wybierzesz „cechy”, które są również z pewnością unikalne dla języka. W przypadku Pythona wybieram cechę używania dwukropków do rozpoczynania zestawu instrukcji, co moim zdaniem jest dość wyjątkową cechą (popraw mnie, jeśli się mylę).

Jeśli w moim przykładzie nie możesz znaleźć dwukropka, aby rozpocząć zestaw instrukcji, przejdź do innej możliwej cechy, powiedzmy, używając defsłowa kluczowego do zdefiniowania funkcji. Może to powodować pewne problemy, ponieważ Ruby używa słowa kluczowego również defdo definiowania funkcji. Kluczem do odróżnienia tych dwóch (Python i Ruby) jest użycie różnych poziomów filtrowania, aby uzyskać najlepsze dopasowanie. Ruby używa słowa kluczowego, endaby zakończyć funkcję, podczas gdy Python nie ma nic do zakończenia funkcji, po prostu usuwa wcięcie, ale nie chcesz tam iść. Ale znowu endmoże to być Lua, kolejny język programowania, który można dodać do mieszanki.

Widać, że języki programowania po prostu nakładają się zbyt mocno. Jedno słowo kluczowe, które może być słowem kluczowym w jednym języku, może być słowem kluczowym w innym języku. Używanie kombinacji słów kluczowych, które często idą w parze, na przykład w języku Java, public static void main(String[] args)pomaga wyeliminować te problemy.

Jak już powiedziałem, największą szansą jest szukanie stosunkowo unikalnych słów kluczowych lub zestawów słów kluczowych, aby oddzielić je od siebie. A jeśli się pomylisz, przynajmniej spróbowałeś.

Williama Lee
źródło
0

Skonfiguruj losowy mieszacz, taki jak

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
źródło
0

Ta witryna wydaje się całkiem dobra w identyfikowaniu języków, jeśli chcesz szybko wkleić fragment kodu do formularza internetowego, zamiast robić to programowo: http://dpaste.com/

drkvogel
źródło