不久前,我听说曾经有一个编译器试图通过分析上下文并推断出意图来修复语法错误。
这样的编译器真的存在吗?显然,它没有什么实用价值,但是玩和学习会很有意思。
不久前,我听说曾经有一个编译器试图通过分析上下文并推断出意图来修复语法错误。
这样的编译器真的存在吗?显然,它没有什么实用价值,但是玩和学习会很有意思。
Answers:
从某种意义上说,编译的行为是在推断某种语法的含义,因此,语法错误就是编译器无法弄清楚的时候。您可以添加更多的“猜测”功能,以使编译器推断出更多的内容,并使语法更加灵活,但是它必须通过一组特定的规则进行推断。这些规则成为语言的一部分,不再是错误。
所以,不,实际上没有这样的编译器,因为这个问题没有道理。猜测根据某些规则要执行的语法错误只是语法的一部分。
从这个意义上讲,有一个很好的编译器示例可以执行此操作:任何C编译器。他们通常只会打印出一些不应该出现的警告,然后假设您的意思是X,然后继续。实际上,这是在“猜测”不清楚的代码(尽管本质上基本上不是语法),同样可能由于错误而停止了编译,因此可以视为错误。
听起来真的很危险。如果编译器试图推断您的意图,推断错误,修复代码,然后不告诉您(或在警告您与所有人一样,忽略您),那么您将在运行可能认真做些破坏。
像这样的编译器可能是故意没有创建的。
目前,用于编程语言的IDE通常以某种方式在后台运行编译器,因此它可以提供分析服务,例如语法着色,IntelliSense,错误等。显然,这样的编译器需要能够理解深陷的代码。在大多数情况下,编辑时代码不正确。但是我们仍然必须理解它。
但是,通常,错误恢复功能仅在编辑时使用。允许在“主线”场景下进行实际编译没有多大意义。
有趣的是,我们确实将该功能内置到JScript.NET编译器中。基本上,有可能将编译器置于一种模式,在这种模式下,即使IDE可以从编译器中恢复,即使遇到错误,我们也可以继续进行编译。您可以输入Visual Basic代码,在其上运行JScript.NET编译器,并有可能在另一端出现正常运行的程序!
这是一个有趣的演示,但是由于很多原因,它对于“主线”场景来说并不是一个很好的功能。完整的解释将很冗长;简短的解释是,它使程序无法正常运行并偶然发生,并且使通过多个编译器或同一编译器的多个版本运行相同的代码变得困难。该功能增加的大笔费用并不能因小额利益而得到证明。
彼得·托尔(Peter Torr)是当天PM的功能发布者,他在2003年的此博客文章中对此进行了简短的讨论。
尽管我们确实通过JScript .NET引擎的脚本托管API公开了此功能,但我不知道有任何真正的客户曾经使用过此功能。
在我看来,如果编译器可以修复错误的语法,则应以该语言记录该语法。
语法错误的原因是因为解析器无法在程序之外创建抽象语法树。当令牌不正确时会发生这种情况。为了猜测该令牌的位置,是否应该删除该令牌,或者是否应该添加其他令牌以修复错误,您需要某种可以猜测程序员意图的计算机。机器如何猜测:
int x = 5 6;
应该是:
int x = 5 + 6;
它可以很容易被任何如下:56
,5 - 6
,5 & 6
。编译器无法知道。
该技术尚不存在。
尽管不是完全一样,但这是HTML变成灾难的原因。浏览器容忍了不良的标记,接下来您知道的是,浏览器A不能像浏览器B那样渲染(是的,还有其他原因,但这是最常见的几种之一,尤其是大约在10年前,一些松散规则成为惯例之前)。
正如埃里克·利珀特(Eric Lippert)推断的那样,其中许多事情最好由IDE处理,而不是由编译器处理。那让您看看自动钻头正在为您搞砸什么。
我认为现在占主导地位的策略是不断完善语言,而不是放松编译器:如果确实是编译器可以自动找出的东西,则围绕它引入定义良好的语言构造。
我想到的直接示例是C#中的自动属性(不是唯一具有类似特征的语言):鉴于任何应用程序中的大多数getter / setter实际上只是围绕字段的包装,因此允许开发人员指出其字段目的,让编译器注入其余的内容。
然后,我想到:大多数C样式语言已经在某种程度上做到了这一点。对于可以自动找出的内容,只需改进语法:
if (true == x)
{
dothis();
}
else
{
dothat();
}
可以简化为:
if (true == x)
dothis();
else
dothat();
最后,我认为可以归结为:趋势是您不会使编译器“更智能”或“更松散”。这是使语言变得更聪明或更宽松的语言。
此外,过多的“帮助”可能很危险,例如经典的“ if”错误:
if (true == x)
if (true == y)
dothis();
else
dothat();
if (x && y) dothis(); else dothat();
看起来会稍微好一点。
true
或false
。
当我在80年代末和90年代初在DEC和IBM小型计算机及大型机系统上对FORTRAN和PL / I进行编码时,我似乎记得编译器会定期注销诸如“等等等等的消息;假设等等等等的消息。” ”。那时,这是批处理和打孔卡(甚至比我更早的时候)时代的遗留物,在提交代码运行和返回结果之间可能要等待很长时间。因此,对于编译器进行第二次猜测,然后继续尝试而不是中止遇到的第一个错误非常有意义。提醒您,我不记得这些“修正”特别复杂。当我最终移到交互式Unix工作站(Sun,SGI等)时,
编译器的目标是产生表现所需的可执行文件。如果程序员写了一些无效的东西,即使编译器可以90%的概率猜到了意图,通常最好还是要求程序员修复程序以使意图更清楚,而不是让编译器继续执行并生成可执行文件。这将有很大的机会掩盖错误。
当然,通常应该对语言进行设计,以使能够清楚表达意图的代码是合法的,而不能清晰表达意图的代码则应被禁止,但这并不意味着它们是合法的。考虑以下代码[Java或C#]
const double oneTenth = 0.1;
const float oneTenthF = 0.1f;
...
float f1 = oneTenth;
double d1 = oneTenthF;
让编译器为该分配添加隐式类型转换f1
将是有帮助的,因为程序员可能只想f1
包含一个逻辑的东西(float
最接近1/10 的值)。但是,与其鼓励编译器接受不正确的程序,不如在某些情况下允许规范隐式从双精度浮点数转换为规范。另一方面,分配给d1
程序员的意图可能是也可能不是,或不是程序员真正想要的,但是没有语言规则禁止它。
最糟糕的语言规则是那些在某些情况下如果不能合法编译的情况下编译器将进行推理的语言规则,但是在意图进行推理的情况下程序可能“偶然地”有效的语言规则。许多涉及隐式声明结尾的情况都属于此类。如果打算编写两个单独的语句的程序员省略了一个语句终止符,则编译器通常可以设法推断出语句边界,但有时可能会将一个应视为两个的东西视为一个语句。
在C语言中,不能按值传递数组,但是编译器允许您编写:
void foo(int array[10]);
然后将其默写为:
void foo(int* array);
这有多愚蠢?我宁愿在这里使用硬错误,也不愿进行无声重写,因为这种特殊规则使许多程序员相信数组和指针基本上是同一回事。他们不是。