"@Golo:所以你想要的是能够指定在每种语言结构之间、在每种上下文中如何出现空白?(例如,if-then-else 如何在 do 循环中与在函数的顶层)?
Golo:没错 :-)"
那么你需要的是访问代码中每个点的语言结构,以及每个语言元素(开始/结束行/列)的精确位置信息。对于 linting,您需要一种针对这些事物组合编写测试的方法。对于repair,您需要一种方法来重新生成满足您的约束的文本。您显然希望所有这些都易于配置。
您想要的“结构”是语法树中的解析器生成的。上下文是围绕感兴趣结构的句法结构。您不需要抽象语法树,因为这会丢失要检查/控制其位置的具体标记,因此您需要完整的具体分析树。
解析器对精确的源位置不感兴趣,但词法分析器(需要将输入流分解为语言标记以提供给解析器)可以收集这些精确信息。您担心“什么构成列调整以及调整多少”的一些复杂问题。一些例子: 制表符:制表符到下一个 8 字符边界? 4个字?到预先指定的选项卡列?在 linux 上,“LF”将行号前移,并将列数重置为 1。在 Windows 上,“CR/LF”是一对。在我遇到的其他操作系统上,它只是“CR”;在真正现代的系统上,Unicode 换行符应该这样做。那么,如果在linux上,应该如何对待CR呢?在文本中找到空字符怎么样? ^Z?其他控制字符(例如,^L [formfeed])?
给定一个源文件,该文件被精确解析为一个带有捕获源位置的 CST,现在您想要检查一个结构是否按照您想要的方式对齐。首先,您需要指定结构;做循环?构造函数?数据声明?然后,您需要对列位置进行谓词来进行精确控制。
几乎所有提供语法树的工具都没有提供任何简单的方法来引用此类结构。几乎你被困在编写经典的类似编译器的过程代码,它知道语法树的形状并爬过它寻找感兴趣的树节点,然后环顾四周看看是否存在其他相关的树节点。进入此模式后,您可以识别所需的树,然后编写更多程序代码来检查间距约定。
程序转换系统 (PTS) 通常提供“源到源”重写,您可以在其中直接使用语言的表面语法编写模式。这比程序上绕树爬要方便得多。一些只做源到源模式对;有些提供仅指定单个模式的能力。 PT 系统还必须能够解析感兴趣的语言,并使您能够为特定任务添加自定义检查。
例如,我们的 DMS Software Reengineering Toolkit 解析 ECMAScript,并提供此类源模式规范,以及附加自定义条件和操作的能力。举个例子:
domain ECMAScript;
pattern ideal_if_statement_layout(e:expression,s:statement):statement =
" if (\e)
\s" if diagnose_not_equal(column(s),parentheses_column(e));
表达了对“if then”语句的兴趣(您可以对“if then else”使用不同的模式),以及对检查语句元素位置的自定义列比较函数的约束。 “diagnose_not_equal”自定义函数会产生 lint-complaints。引号是元引号;它们是模式匹配语言的一部分,而不是底层语言。 e 和 s 是元变量,分别匹配任何语言结构表达式和语句。因为这些正在应用于 CST,所以它们不能与预期的目标不匹配。自定义函数“column”只获取与s的最左边子树相关联的起始列信息; DMS 中的树管理 API 使这基本上变得微不足道。需要“括号列”,因为该模式会告诉您 e 的位置; "(" 位于 e 上方的树节点中,因此需要稍微导航树以找到 "(",然后提取其 最右边 列使用 DMS 树 API 轻松完成。
您可以构建任意复杂的模式;您也可以在一种模式中设定条件,取决于另一种模式的匹配。因此,使用适量的自定义列提取函数,您可以编写各种 linting 检查。
这不会让您轻松检查“if”关键字是否位于“(”关键字左侧的一个空格。您可以在某种程度上通过添加自定义检查来表达,例如“statement_keyword_column”等,但这开始变得尴尬了。
您可能会注意到模式的布局;也可以将其用作约束。 DMS 不提供执行此操作的直接方法。但是,它完全能够将自己的模式描述读取为树。使用它,可以提取图案的明显布局,并使用它来检查结构布局。这需要对 DMS 的使用有一定的了解,但这是一个汗水问题,而不是理论或缺失的机制。
我个人不太喜欢布局上的 linting;我希望文件只是被重新塑造。
DMS 确实有漂亮的打印规则,可以将您的 CST(无论其布局是什么)转换为受其漂亮打印规则控制的布局。目前,这些规则是特定于树节点的,并且是用语法编码的,所以它们受到了一些限制。可以写(在语法中):
stmt = 'if' expression stmt ';'
<<PrettyPrinter>>: { V(H('if,expression),I(stmt[1])) }
这将导致所有 if-then 语句重新生成为:
if expresssion
stmt
[V 表示两个子框的“垂直框”; H 表示“水平框”,I 表示“缩进框”]
仔细使用这些漂亮的打印规则可以很好地重新格式化代码。它并不完美,因为您无法以这种方式控制多个语句的布局。但这是 DMS 的一部分,实际上很容易修改。
理想的解决方案是使用模式语言,并使用模式中的布局来控制漂亮打印。这在我们的计划中,但遗憾的是,还没有在 DMS 中。
我认为其他 PTS 可以在某种程度上表达上述模式,并且它们中的大多数都有某种方式来指定像 DMS 那样的漂亮打印。所以好消息是这些工具可以满足您的大部分需求。不太好的消息是选择其中一种工具并学会使用它需要付出很大的努力。一个下午还远远不够。