从代码片段检测编程语言


115

检测代码段中使用哪种编程语言的最佳方法是什么?


1
几乎有无数种语言...您想检测其中的任何一种吗?还是我们只是在谈论流行的?
斯潘塞·鲁波特

仅是流行的(C / C ++,C#,Java,Pascal,Python,VB.NET,PHP,JavaScript以及Haskell)。
若昂·马托斯

12
Haskell不会受欢迎,因为我从未听说过。;-)
斯蒂芬妮(Stephanie),

22
如果您还没有听说过Haskell,那么您可能对编程语言不太了解。
Akhorus

4
有了这个在线服务,它做的:algorithmia.com/algorithms/PetiteProgrammer/...
班尼纽格鲍尔

Answers:


99

我认为垃圾邮件过滤器中使用的方法效果很好。您将代码片段拆分为单词。然后,您可以将这些单词与已知代码段的出现情况进行比较,并针对您感兴趣的每种语言,计算此代码段以语言X编写的可能性。

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

如果您具有基本的机制,那么添加新语言非常容易:只需用新语言的一些代码片段训练检测器(您可以将其提供给一个开源项目)。通过这种方式,它了解到“ System”很可能出现在C#代码片段中,而“ puts”可能出现在Ruby代码片段中。

实际上,我已经使用这种方法将语言检测添加到论坛软件的代码片段中。除有歧义的情况外,它100%的时间有效:

print "Hello"

让我找到代码。

我找不到代码,所以我做了一个新的代码。这有点简单,但适用于我的测试。当前,如果您提供的Python代码比Ruby代码多得多,则可能是这样的代码:

def foo
   puts "hi"
end

是Python代码(尽管实际上是Ruby)。这是因为Python也有一个def关键字。所以,如果它已经看到1000X def在Python和100X def在Ruby中那么它仍然可以说Python的,即使putsendRuby是特定的。您可以通过跟踪每种语言看到的单词并将其除以某个地方(或通过为每种语言提供相等数量的代码)来解决此问题。

我希望它可以帮助您:

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
我在NLP课堂上做了类似的事情,但我们又走了一步。您不喜欢看单个单词的频率,而是看成对的单词和三倍的单词。例如,“ public”可能是许多语言中的关键字,但是“ public static void”在C#中更为常见。如果找不到三元组,则退回到2,然后
再降为1。– mpen

1
可能还想考虑将单词拆分的位置。在PHP中,变量以开头$,因此也许您应该在字词界限上进行拆分,因为$应当坚持使用变量。运算符喜欢=>并且:=应该作为一个单独的令牌粘在一起,但是OTH您可能应该{s 周围分开,因为它们始终独立存在。
mpen

2
是的 一种避免完全拆分的方法是使用ngram:每个n个长度的子字符串。例如,“ puts foo”的5克是“ puts”,“ uts f”,“ ts fo”和“ s foo”。这种策略看似很奇怪,但它的效果比您想象的要好,这不是人类如何解决问题的方法。要确定哪种方法更好,您必须同时测试这两种方法
Jules 2010年

2
不过,某些语言的语法很少。我还推测,通用变量名将主导该语言的关键字。基本上,如果您的训练数据中有一段由匈牙利人编写的C代码,带有匈牙利语的变量名称和注释,则任何包含匈牙利语的其他源都可能被确定为“相似”。
Tripleee 2011年

26

其他人解决的语言检测:

Ohloh的方法:https : //github.com/blackducksw/ohcount/

Github的方法:https : //github.com/github/linguist


4
我检查了这两种解决方案,但都不能完全按照要求进行。他们主要查看文件扩展名以确定语言,因此,在没有扩展名线索的情况下,他们不一定可以检查代码段。
Hawkee

5
Github的方法现在也包括贝叶斯分类器。它主要基于文件扩展名检测语言候选者,但是当文件扩展名与多个候选者匹配时(例如“ .h”-> C,C ++,ObjC),它将标记输入代码样本并根据预先训练的集合进行分类数据的。Github版本可以被迫始终扫描代码,而无需查看扩展名。
Benzi



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甚至无法描述所有语言。如果允许您重新定义关键字并创建宏,它将变得更加困难。Alså,当我们谈论一个代码片段时,您将不得不与BNF进行部分匹配,这更加困难且更容易出错。

2

好难题。

我认为不可能检测所有语言。但是您可以触发关键令牌。(某些保留字和常用字符组合)。

Ben有很多语法相似的语言。因此,这取决于代码段的大小。


1

Prettify是一个Javascript程序包,可以很好地检测编程语言:

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

它主要是语法突出显示器,但是可能存在一种提取检测部分的方法,目的是从摘要中检测语言。


1
经过进一步检查,似乎美化并没有实际检测到该语言,但根据每个元素的语法突出显示了该语言。
Hawkee 2012年



0

我认为不会有简单的方法来完成此操作。我可能会生成某些语言/某些语言类所特有的符号/常用关键字列表(例如,C风格语言的花括号,BASIC语言的Dim和Sub关键字,Python的def关键字,功能语言的let关键字) 。然后,您也许可以使用基本语法功能来进一步缩小范围。


0

我认为语言之间的最大区别在于其结构。因此,我的想法是研究所有语言中的某些共同要素,并了解它们之间的差异。例如,您可以使用正则表达式来选择诸如:

  • 功能定义
  • 变量声明
  • 类声明
  • 注释
  • for循环
  • while循环
  • 打印报表

也许大多数语言应该具备的其他一些功能。然后使用一个点系统。如果找到正则表达式,则对每个元素最多奖励1分。显然,某些语言将使用完全相同的语法(因为循环经常像for(int i=0; i<x; ++i)这样编写,因此多种语言可以为同一事物得分,但是至少您要减少它成为完全不同的语言的可能性)。其中一些可能在整体上得分为0(例如,该代码段根本不包含任何功能),但是那很好。

将此与Jules的解决方案结合起来,它应该可以很好地工作。也许还会寻找关键词的频率来加分。


0

有趣。我有一项类似的任务,可以识别不同格式的文本。是YAML,JSON,XML还是Java属性?例如,即使存在语法错误,我也应该自信地将JSON与XML区别开。

我认为我们如何对问题建模是至关重要的。正如Mark所说,单字标记化是必要的,但可能还不够。我们将需要二元组,甚至三元组。但是我想知道我们正在研究编程语言,因此我们可以走得更远。我注意到几乎所有的编程语言都具有两种独特的令牌类型- 符号关键字。符号相对容易识别(某些符号可能是文字,而不是语言的一部分)。然后,符号的双字母组或三字母组将围绕符号选取唯一的语法结构。如果培训足够大且足够多样化,则关键字是另一个容易的目标。一个有用的功能可能是围绕可能的关键字的双语法例。令牌的另一种有趣类型是空格。实际上,如果我们以通常的方式用空格标记,则会丢失此信息。我想说,在分析编程语言时,我们保留空白标记,因为这可能包含有关语法结构的有用信息。

最后,如果选择随机森林之类的分类器,则将爬网github并收集所有公共源代码。大多数源代码文件都可以用文件后缀标记。对于每个文件,我将在空行处将其随机分成各种大小的代码段。然后,我将提取特征并使用标记的片段来训练分类器。训练完成后,可以测试分类器的准确性和召回率。


0

我遇到的最好的解决方案是在Ruby on Rails应用程序中使用语言学家。这是一种特定的实现方式,但确实可行。@nisc在上面提到了这一点,但是我会告诉您我使用它的确切步骤。(以下某些命令行命令特定于ubuntu,但应轻松转换为其他操作系统)

如果您不介意暂时使用任何Rails应用程序,请在其中创建一个新文件以插入有问题的代码段。(如果您没有安装rails,这里有一个很好的指南尽管对于ubuntu我还是推荐这样做。然后运行rails new <name-your-app-dir>并cd进入该目录。运行rails应用程序所需的一切已经存在)。

使用了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 updatesudo apt-get install makesudo 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在应用程序的目录中运行。

似乎需要做很多额外的工作,特别是如果您还没有Rails,但是如果按照以下步骤操作,实际上您不需要了解Rails的任何知识,而我只是真的没有找到更好的方法来检测文件/代码段的语言。


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

如果您想以一种快速的方式将摘要粘贴到Web表单中,而不是通过编程方式进行操作,则该站点似乎非常擅长识别语言:http : //dpaste.com/

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.