从代码片段中检测编程语言

133

如何最好地检测代码片段中使用的编程语言?


1
有无数的编程语言存在...你想检测所有的语言吗?还是只关注流行的语言? - Spencer Ruport
1
只需要流行的编程语言(C/C++、C#、Java、Pascal、Python、VB.NET、PHP、JavaScript,以及可能的Haskell)。 - João Matos
16
好的,Haskell不可能很受欢迎,因为我从未听说过它。;-) - Stephanie Page
24
如果你没听说过 Haskell,那么很可能你对编程语言了解得不多。 - Akhorus
4
这里有一个在线服务可以实现这个功能:https://algorithmia.com/algorithms/PetiteProgrammer/ProgrammingLanguageIdentification。 - Benny Code
显示剩余2条评论
17个回答

104
我认为垃圾邮件过滤器中使用的方法非常有效。您需要将代码片段分成单词,并将这些单词出现的次数与已知代码片段进行比较,计算每种语言的概率,从而判断该片段是否是特定语言。如果你想添加新的语言,只需用该语言的几个代码片段(可以使用开源项目)训练检测器即可。这样它就会学习到在C#片段中,“System”有可能出现,在Ruby片段中,“puts”有可能出现。我实际上已经使用了这种方法来为论坛软件的代码片段添加语言检测功能。除了模棱两可的情况外,它的工作效果达到了100%。
参考链接:http://en.wikipedia.org/wiki/Bayesian_spam_filtering
print "Hello"

让我找一下代码。

我没找到这段代码,所以我写了一个新的。虽然有点简单,但对于我的测试来说它可行。目前,如果您输入的Python代码比Ruby代码多得多,则很可能会出现以下提示:

def foo
   puts "hi"
end

这段代码是Python(其实是Ruby)的代码。这是因为Python也有一个关键字def。如果它在Python中看到了1000个def,在Ruby中看到了100个def,那么即使putsend是Ruby特有的,它仍可能会说这是Python。你可以通过跟踪每种语言看到的单词并在某处进行除法运算(或者给它相等数量的每种语言的代码)来解决这个问题。

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)

2
我还需要在论坛软件中使用它。谢谢关于贝叶斯过滤的提示。 - João Matos
13
在我的自然语言处理课上,我做了类似的事情,但我们更进一步了。你不仅要查看一个单词的频率,还要查看成对和三个词组的频率。例如,“public”可能是许多语言中的关键字,但在C#中,“public static void”更普遍。如果找不到三个词组,就回退到两个词组,然后是一个词。 - mpen
2
是的。避免完全分割的一种方法是使用ngrams:您可以获取每个n长度的子字符串。例如,“puts foo”的5元组为“puts”、“uts f”、“ts fo”和“s foo”。这种策略可能看起来很奇怪,但它比您想象的要好,只是不是人类解决问题的方式。要决定哪种方法更好,您需要测试两种方法... - Jules
2
有些编程语言的语法非常简单。我还猜测,常见的变量名可能会占据语言关键字的主导地位。基本上,如果你的训练数据中有一段由匈牙利人编写的C代码,其中包含匈牙利语的变量名和注释,那么任何其他包含匈牙利语的源代码都很可能被确定为“相似”。 - tripleee
1
如果有人想要开始而没有接受过培训:https://github.com/anvaka/common-words - sebilasse
显示剩余5条评论

27

5
我检查了这两个解决方案,但它们都不能完全满足要求。它们主要根据文件扩展名来确定语言,因此在没有扩展名的情况下无法准确检查代码片段。 - Hawkee
5
Github现在的方法也包括贝叶斯分类器。它主要基于文件扩展名来检测语言候选项,但当一个文件扩展名匹配多个候选项(例如“.h”--> C、C++、ObjC)时,它将对输入代码样本进行分词,并根据预先训练好的数据集进行分类。Github版本可以强制始终扫描代码,而不看扩展名。 - Benzi

11

6
一种替代方案是使用highlight.js,它可以执行语法高亮,但使用高亮处理的成功率来识别语言。原则上,任何语法高亮器代码库都可以以同样的方式使用,但highlight.js的好处在于语言检测被认为是一个功能,并且用于测试目的更新:我尝试过这个方法,但效果不佳。压缩的JavaScript完全混淆了它,即分词器对空格很敏感。一般来说,仅计算高亮命中次数似乎不太可靠。更强大的解析器或者可能是不匹配的部分计数可能会更好。

highlight.js 中包含的语言数据仅限于用于代码高亮所需的值,这对于语言检测来说相当不足(尤其是对于少量代码)。 - Adam Kennedy
我认为没问题,可以通过这个 fiddle 进行检查:https://jsfiddle.net/3tgjnz10/ - sebilasse

4

首先,我会尝试找到特定语言的关键词,例如:

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

3
问题在于这些关键词仍然可以出现在任何语言中,无论是作为变量名还是字符串。此外,使用的关键词存在很大的重叠。你需要做的不仅仅是查找关键词。 - mpen

4
这很困难,有时甚至是不可能的。这段简短的代码片段使用的是哪种语言?
int i = 5;
int k = 0;
for (int j = 100 ; j > i ; i++) {
    j = j + 1000 / i;
    k = k + i * j;
}

(提示:可能是多个语言之一。)

您可以尝试分析各种语言,并尝试使用关键字频率分析来决定使用哪种语言,例如在文本中某些关键字集以某些频率出现,则很可能使用的是Java等语言。但我认为您不会得到完全可靠的结果,因为您可以将C语言中的变量命名为与Java中的关键字相同的名称,从而骗过频率分析。

如果您增加了复杂性,您可以寻找结构信息,例如某个关键字总是在另一个关键字之后出现,这将为您提供更多线索。但要设计和实施这些方法也会更加困难。


29
如果有多种语言可能,探测器可以将所有可能的候选项都列出来。 - Steven Haryanto
或者,它可以给出第一个匹配的语言。如果实际使用情况类似于语法高亮,则这确实没有任何区别。这意味着任何匹配的语言都将正确地突出显示代码。 - jonschlinkert

2
我遇到的最好的解决方案是在 Ruby on Rails 应用程序中使用 linguist gem。这种方法有点特殊,但它确实有效。@nisc 上面已经提到了这一点,但我将告诉您使用它的确切步骤。(以下一些命令行命令是针对 Ubuntu 特定的,但应该很容易翻译成其他操作系统)
如果您有任何不介意暂时搞乱的 Rails 应用程序,请在其中创建一个新文件以插入您所需要的代码片段。(如果您没有安装 Rails,则有一个很好的指南 here,尽管对于 Ubuntu,我建议使用 this。然后运行 rails new <name-your-app-dir> 并进入该目录。运行 Rails 应用程序所需的一切都已经在那里)。
在您有一个要使用此功能的 Rails 应用程序之后,在 Gemfile(就在您的应用程序目录中,没有扩展名)中添加 gem 'github-linguist'
首先安装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>)。它会告诉您类型、MIME类型和语言。对于多个文件(或与ruby/rails应用程序一般使用),可以在应用程序目录中运行bundle exec linguist --breakdown
这似乎需要很多额外的工作,特别是如果您没有rails,但是如果您遵循这些步骤,实际上您不需要了解任何关于rails的知识,我还没有找到更好的检测文件/代码片段语言的方法。

2
我需要这个,所以我创建了自己的。 https://github.com/bertyhell/CodeClassifier 它非常易于通过在正确的文件夹中添加训练文件进行扩展。 用C#编写。但我想代码很容易转换成任何其他语言。

2

这取决于你有哪种类型的代码片段,但我会通过一系列的标记器运行它,并查看它在哪种语言的BNF下是有效的。


所有的编程语言都无法用BNF来描述。如果你被允许重新定义关键字和创建宏,那么这将变得更加困难。而且,由于我们正在讨论代码片段,你必须对BNF进行部分匹配,这会更加困难且容易出错。 - user14070

2

如果您想快速将一小段代码粘贴到网页表格中,而不是通过编程实现,那么这个网站似乎非常擅长识别语言:http://dpaste.com/


它在我尝试的所有语言上都失败了,甚至包括维基百科上的简单代码片段“Hello World”lol,它被检测为Java。一个alert('hello world!');被猜测为C++。 - Jack
如果你有一个名为alert()的函数,那么alert('hello world!');可能是有效的C++代码... 它可能是根据语法猜测的,而不是每种语言的标准库。 - drkvogel
它甚至没有考虑到它只是一个双引号内的简单字符串... - Jack
哦,我的错误 - 我没有注意到它们是单引号,所以那不是有效的C ++。 - drkvogel
实际上,如果您使用g++ -fpermissive编译,由单引号括起来的多个字符的“字符串”会生成一个警告,而不是错误 - 所以这是有效的C ++,尽管不好。 - drkvogel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接