从学术意义上讲,正则表达式是否可以用作编程语言?
我好奇的动机是我刚刚看了一个SO问题,问“正则表达式可以X吗?”。这让我想知道,对于使用它们的可能解决方案,可以从一般意义上说什么。
我基本上是在问,“图灵正则表达式是否完整”?
从学术意义上讲,正则表达式是否可以用作编程语言?
我好奇的动机是我刚刚看了一个SO问题,问“正则表达式可以X吗?”。这让我想知道,对于使用它们的可能解决方案,可以从一般意义上说什么。
我基本上是在问,“图灵正则表达式是否完整”?
Answers:
正则表达式是一种特殊形式的语法,用于解析字符串和其他文本信息,这些信息在形式语言理论中被称为“规则语言”。它们本身不是编程语言。它们是编码的简写方式,否则,与有时看起来晦涩难懂的Regex相比,实现起来非常繁琐,甚至更加令人困惑。
编程语言通常定义为Turing Complete的语言。这样的语言必须能够处理任何可计算的功能。正则表达式不适用于此类别。
如果要使用类似于Regex的语言,请尝试J。
如果辩论的参与者使用X和Y的不同定义,则很难回答“是X a Y ” 类型的问题。对于某些定义,答案可能是“是”,对于某些定义,答案是“否”。特别是如果答案取决于技术细节,则不同的定义会有所不同。此外,此讨论还包含一些错误信息,因此请耐心等待更长的答案。
“ 编程语言 ” 是什么意思?
一个简单的答案可能是“一种用于创建程序的语言”。当然可以,但是:什么样的程序?可以用来创建某些程序而不是其他程序的语言呢?以下是两个具体示例来说明极端情况:
1)一种称为M的虚构语言的工作方式如下:如果程序包含单个字母“ m”,它将创建一个Minesweeper游戏。其他所有内容都是语法错误。
直观上,这不是我们所说的“一种编程语言”的意思。但是M的营销部门可能会争辩说它在技术上满足了该定义,因为它可以用于创建程序。当然,编译器为您做了一些关键的部分,但这就是编译器所做的,不是吗?C语言的编译器还将一些简单的单词转换为数十个处理器指令。M编译器可以进一步简化您的工作。
2)如果安装了著名的Turbo Pascal的原始版本,则可以编写许多程序。但是您不能编写在Web浏览器中运行的游戏,因为根本就没有必要的API。
那么究竟什么使Turbo Pascal成为一种编程语言,而M没有呢?简而言之,用Pascal可以比用M 做更多的事情。但是,想象一下我们有一个M.NET,它创建了一个在网络浏览器中运行的Minesweeper游戏。因此,现在我们有了Pascal可以做而M.NET无法做的事情,但是我们还有M.NET可以做而Pascal无法做的事情。为什么我们认为Pascal的优点很重要,而M.NET的优点却无关紧要?
答案是,您可以在Pascal中编写各种算法,但不能在M或M.NET中编写算法。当然,M会编译您的命令“ m”,而C会编译您的命令“ strcmp”。但是您可以将“ strcmp”放在更大的上下文中,例如逐行比较两个文件,或者读取数千个字符串并按字母顺序对它们进行排序,或者……还有数百万其他内容。正是这种能力可以在任何算法中使用给定命令,这构成了编程语言的本质。
究竟什么是算法,更重要的是什么是“任何算法”?在计算机科学中,我们使用Turing-complete一词。这个想法是有一套计算机语言,每种语言都可以模拟所有语言。图灵机就是这些语言之一,这就是为什么要这样称呼它们。Pascal在那儿,C在那儿,Java在那儿,Python在那儿,Lisp在那儿,Smalltalk在那儿,甚至XSLT在那儿。我们假设的M和M.NET 不存在。您可以在任何提供一门不错的计算机科学课程的大学中了解更多有关该知识的信息,但是您的想法是,使用图灵完备的语言可以做任何事情如果您给他们提供了最低限度的必需API,那么另一种图灵完备的语言可以做到。(如果您向Pascal提供了一些Web浏览器API,则可以在Web浏览器中创建各种游戏。如果您向M提供Web浏览器API,则仍然只能创建Minesweeper。)我们可以隐喻地说,如果您从编程语言中删除了所有API,重要的是剩下的。
“ 正则表达式 ” 是什么意思?
不同的编程语言对它们的实现略有不同。但是最初的想法是正则表达式表达所谓的正则语言。请注意,我们在这里不是在谈论编程语言,而是在谈论(伪)人类语言。想象一下,您发现一些异国部落说一种仅由单词“ ba”,“ baba”,“ bababa”等组成的语言。您可以将这种语言口头描述为“一个或多个重复的音节'ba'”,或使用正则表达式为“(ba)+”。
正则表达式应该表示:“无”,“这封信”,“此后跟那个”,“此或那个”,“此重复一次或多次”和“不是此”。-这是数学定义。其他一切只是从先前组件构建的便捷快捷方式。例如,“ this,重复两次或三遍”可以被翻译为“ this,其次是this,然后是(this or none)”,但是写“ ba {2,3}”比“ baba”更方便(ba)?”。
在现实生活中,一个典型的实现的“正则表达式”工具多莫过于此。例如,使用数学定义,语言为“ aba”,“ aabaa”,“ aaabaaa”等-任意数量的“ a”,后跟“ b”,再跟相同数量的“ a” “ s- 不是常规语言。但是,今天使用的许多“正则表达式”可以使用“(a +)b \ 1”这样的附加概念“我们之前发现的相同事物”来检测到它。使用这个附加概念,我们可以做一些很酷的事情,例如,检测由素数组成的单词。不过,我们无法执行任何算法 ...以解释原因,
因此,回到最初的主题:正则表达式(定义为:描述Chomsky层次结构中的正则语言的表达式;还是:前者,加上\ 1操作)是一种编程语言(定义为:Turing-complete)?答案是否定的。不,您不能使用正则表达式实现任何算法,而实现任何算法的能力正是研究计算机科学的人们通常将其理解为编程语言的本质。
当然,任何人都可以通过坚持不同的定义来改变答案。正如我在开始时所写的那样,技术细节在这里很重要。如果您弄错了,您将得到错误的答案。
如果您对技术细节不感兴趣,那么答案可能是:您可以使用正则表达式(而不是其他任何东西)来编写程序吗?否。为什么要称它为编程语言?(但是,在这里下载并删除了这样的答案,这就是为什么我写了这个更长的版本。)
编辑:而且,任何人都可以创建一个实现自己的“正则表达式”新变体并添加一些新功能的库。在某些时候,新功能可能足以使整个系统成为图灵完备的。一个简单的例子是使用一些新语法嵌入图灵完备的语言。但它的发生也不太明显。也许已经发生了。
在.Net中,Regex不仅可以使用交替和环视的不同组合来处理多种形式的条件,而且还可以操纵其自身的堆栈。
(?xm)
(?>
<(?<Tagname>table)[^>]*>
)
(?(Tagname)
(
</(?(?!\k'Tagname')(?<-Tagname>))*\k'Tagname'>(?<-Tagname>)
|
(?>
<(?<Tagname>[a-z][^\s>]*)[^>]*>
)
|
[^<]+
)+?
(?(Tagname)(?!))
)
例如,这是我为检索HTML表而编写的一小段代码。与其他正则表达式引擎不同,此控件控制捕获集合(推送,窥视和弹出)的堆栈,并可以处理嵌套对象。我有一个比较复杂的文件,但它有点专有。
我认为在此示例中,可以将Regex视为具有编程语言的所有基本要求。它具有变量,内联内存,条件,输入和输出,它使用多个正则表达式编译引擎(在本例中为.Net)之一进行编译。
为了应对与Regex对(NEVER)Parse HTML的过度使用,我继续发布了可以输入的预键入响应:解析HTML
另一个示例(仅是一个示例)如下:
Function Regex("<(td>)((?:[^<]*(?(?!</\1)<))*)</\1")
Group(0) = "<"
Group(1) = "td>"
Group(0) += Group(1)
Group(2) = LoopMethod()
Group(0) += Group(2)
Group(0) += "</" & Group(1)
Return Group()
End Function
Function LoopMethod()
retGroup = ""
Do
tmpGroup = Everything that is NOT an Opening HTML Delimeter
If the Text following tmpGroup Does NOT Equal "</" & Group(1) Then
tmpGroup += "<"
retGroup += tmpGroup
Else
Exit Do
End If
Loop
Return retGroup
End Function
再次,对于HTML鹦鹉:解析HTML
这显示了执行循环和条件(算法?)的更简单的正则表达式。唯一缺少的是实际的数学计算。这是一个更详细的正则表达式,它比典型的“(。*?)”方法更有效地提取TD单元。
但是,即使作为Regex的狂热者和自称高手,我也不会告诉别人Regex是一种编程语言。我自己对自己的观点是,它不能独立存在,它必须通过自己的引擎运行,同时还要有另一个编程语言引擎的支持。
尽管正则表达式中的一种查找/替换不是一种图灵完备的编程语言,如先前的答案所述,但如果允许重复使用正则表达式进行替换,那么可以使用正则表达式对任何图灵机进行编码:
因此,您可以使用相同的搜索来计算任何可计算的函数,并一遍又一遍地替换javascript正则表达式。
为了证明图灵完备性,只需在正则表达式搜索/替换中对图灵机进行编码就足够了。假设编辑器的状态为:
0000#12345:01-5:0#0000000
可以将其读为带有阅读器的符号带:
[left symbols]#[set of states]:[set of symbols]-[current state]:[current symbol]#[right symbols]
对于在状态5中读取0的规则,写入1并将其状态更改为3并向左移动,我们使用以下表示法对其进行抽象:
5:0 => 1, 3:[left]
我们将前面的符号编码为搜索正则表达式:
(\d)#(1)(2)(3)(4)(5):(0)(1)-5:0#
及其替换表达式(类似于javascript)
#12345:01-$4:$1#$8
好的,现在如何编码许多规则?我们将or
运算符与运算符连接使用|
,然后将结果合并在替换中,用偏移量对组号进行编号。例如,让我们考虑四个规则的集合。
5:0 => 1, 3:left
3:0 => 1, 5:right
5:1 => 1, 5:right
3:1 => 1: 3:stop
我们将它们编码为搜索并替换表达式:
Search:
(\d)#(1)(2)(3)(4)(5):(0)(1)-5:0#|#(1)(2)(3)(4)(5):(0)(1)-3:0#(\d)|#(1)(2)(3)(4)(5):(0)(1)-5:1#(\d)|#(1)(2)(3)(4)(5):(0)(1)-3:1#
Replace by:
$15$23#12345:01-$4$13$21$27:$1$16$24$31#$8
在您喜欢的JavaScript引擎中尝试一下:
function turingstep(s) {
return s.replace(/(\d)#(1)(2)(3)(4)(5):(0)(1)-5:0#|#(1)(2)(3)(4)(5):(0)(1)-3:0#(\d)|#(1)(2)(3)(4)(5):(0)(1)-5:1#(\d)|#(1)(2)(3)(4)(5):(0)(1)-3:1#/g,"$15$23#12345:01-$4$13$21$27:$1$16$24$31#$8");
}
var tape = "0000#12345:01-5:0#0000000"
for(var i = 0; i < 6; i++) {
console.log(tape)
tape = turingstep(tape)
}