我试图在Rascal中为cmake语言的一个子集制作一个DSL,但在调用implode
时很难调试IllegalArgument异常。
CMake.rsc:
module CMake
import util::IDE;
import ParseTree;
import String;
import IO;
import Node;
import Implode;
import AST;
import Syntax;
import Parse;
private str STM_LANG = "CMakeLists";
private str STM_EXT = "cmake"; // change to txt
public void main() {
registerLanguage(STM_LANG, STM_EXT, Tree(str src, loc location) {
print("parse\n");
return parse(src, location);
});
contribs = {
builder(set[Message] (start[Build] pt) {
ctl = implode(pt);
out = (pt@\loc)[extension="txt"];
class = split(".", out.file)[0];
print("build\n");
// writeFile(out, compile(class, ctl));
return {};
})
};
registerContributions(STM_LANG, contribs);
}
Syntax.rsc:
module Syntax
extend lang::std::Layout;
start syntax Build = build: Section+ sections;
layout Layout = Comment*;
lexical Comment = @category="Comment" "#" ![\n]* [\n];
syntax Section = section: Target target Options+ options;
syntax Target = target: "set" "(" Id targetMacro Id targetName ")";
syntax Options = options: IdeFolder Dirs Sources+;
syntax IdeFolder = ideFolder: "set" "(" "$" "{" Id targetMacro "}" "_IDE_FOLDER" "\"" Id ideFolderPath "\"" ")";
syntax Dirs = dirs: "set" "(" "$" "{" Id targetMacro "}" "_DIRS" DirsPath+ dirPaths ")";
syntax DirsPath = dirPaths: "$" "{" Id pathMacro "}" Id? path;
syntax Sources = sources: "set" "(" "$" "{" Id targetMacro "}" ("_SOURCES" | "_WINDOWS_SOURCES" ) Id+ sourceFiles ")";
lexical Id = ([a-zA-Z/.][a-zA-Z0-9_/.]* !>> [a-zA-Z0-9_/.]) \ Reserved ;
keyword Reserved = "set" | "$" | "{" | "}" | "_IDE_FOLDER" | "_DIRS" | "_SOURCES" | "_WINDOWS_SOURCES";
AST.rsc:
module AST
data Build = build(list[Section] sections);
data Section = section(Target target, list[IdeFolder] ideFolders);
data Target = target(str targetMacro, str targetName);
data Options = options(IdeFolder ideFolder);
data IdeFolder = ideFolder(str targetMacro, str ideFolderPath);
data Dirs = dirs(str targetMacro, list[DirsPath] dirPaths);
data DirsPath = dirPaths(str pathMacro, str path);
data Sources = sources(str targetMacro, list[str] sourceFiles);
anno loc Build@location;
anno loc Section@location;
anno loc Options@location;
anno loc Target@location;
anno loc IdeFolder@location;
anno loc Dirs@location;
anno loc DirsPath@location;
anno loc Sources@location;
Parse.rsc:
module Parse
import Syntax;
import ParseTree;
public start[Build] parse(str src, loc origin) = parse(#start[Build], src, origin);
public start[Build] parse(loc origin) = parse(#start[Build], origin);
Implode.rsc:
module Implode
import Parse;
import AST;
import ParseTree;
import Node;
public Build implode(Tree pt) = implode(#Build, pt);
public Build load(loc l) = implode(#Build, parse(l));
CMakeLists.cmake:
# test
set(TARGET_NAME bb)
set(${TARGET_NAME}_IDE_FOLDER "test")
set(${TARGET_NAME}_DIRS
${BLA_PATH}/src
${BLA_GEN_PATH}
${BLA_GEN_PATH}/dir1/pub
${BLA_GEN_PATH}/dir2/pub
)
set(${TARGET_NAME}_SOURCES
../inc/file3.h
../inc/file4.h
)
错误消息是:
|std:///ParseTree.rsc|(17152,5150,<507,0>,<652,60>): IllegalArgument(appl(prod(label("build",sort("Build")),[label("sections",\iter-seps(sort("Section"),[layouts("Standard")]))],{}),[appl(regular(\iter-seps(sort("Section"),[layouts("Standard")])),[appl(prod(label("section",sort("Section")),[label("target",sort("Target")),layouts("Standard"),label("options",\iter-seps(sort("Options"),[layouts("Standard")]))],{}),[appl(prod(label("target",sort("Target")),[lit("set"),layouts("Standard"),lit("("),layouts("Standard"),label("targetMacro",lex("Id")),layouts("Standard"),label("targetName",lex("Id")),layouts("Standard"),lit(")")],{}),[appl(prod(lit("set"),[\char-class([range(115,115)]),\char-class([range(101,101)]),\char-class([range(116,116)])],{}),[char(115),char(101),char(116)]),appl(prod(layouts("Standard"),[conditional(\iter-star(lex("WhitespaceOrComment")),{\not-follow(\char-class([range(9,13),range(32,32),range(133,133),range(160,160),range(5760,5760),range(6158,6158),range(8192,8202),range(8232,8233),range(8239,8239),range(8287,8287),range(12288,12288)])),\not-follow(lit("//"))})],{}),[appl(regular(\iter-star(lex("WhitespaceOrComment"))),[])[@loc=|project://Instrumentation/src/CMakeLists.cmake|(15,0,<4,3>,<4,3>)]])[@loc=|project://Instrumentation/src/CMakeLists.cmake|(15,0,<4,3>,<4,3>)],appl(prod(lit("("),[\char-class([range(40,40)])],{}),[char(40)]),appl(prod(layouts("Standard"),[conditional(\iter-star(lex("WhitespaceOrComment")),{\not-follow(\char-class([range(9,13),range(32,32),range(133,133),range(160,160),range(5760,5760),range(6158,6158),range(8192,8202),range(8232,8233),range(8239,8239),range(8287,8287),range(12288,12288)])),\not-follow(lit("//"))})],{}),[appl(regular(\iter-star(lex("WhitespaceOrComment"))),[])[@loc=|project://Instrumentation/src/CMakeLists.cmake|(16,0,<4,4>,<4,4>)]])[@loc=|project://Instrumentation/src/CMakeLists.cmake|(16,0,<4,4>,<4,4>)],appl(prod(lex("Id"),[conditional(seq([\char-class([range(46,47),range(65,90),range(97,122)]),conditional(\iter-star(\char-class([range(46,57),range(65,90),range(95,95),range(97,122)])),{\not-follow(\char-class([range(46,57),range(65,90),range(95,95),range(97,122)]))})]),{delete(keywords("Reserved"))})],{}),[appl(regular(seq([\char-class([range(46,47),range(65,90),range(97,122)]),conditional(\iter-star(\char-class([range(46,57),range(65,90),range(95,95),range(97,122)])),{\not-follow(\char-class([range(46,57),range(65,90),range(95,95),range(97,122)]))})])),[char(84),appl(regular(
at *** somewhere ***(|std:///ParseTree.rsc|(17152,5150,<507,0>,<652,60>))
at implode(|project://Instrumentation/src/Implode.rsc|(131,2,<9,48>,<9,50>))
at Anonymous Function(|project://Instrumentation/src/CMake.rsc|(481,12,<25,9>,<25,21>))
at $root$(|main://$root$|)
...truncated here...
酷项目!
在@Matty将示例代码拆分为不同的模块后,我们消除了不同语法和数据类型之间混淆的可能性。
所以这是一个调试会话:
rascal>x = parse(#start[Build], |project://rascal-test-project/src/CMakeLists.cmake|);
start[Build]: (start[Build]) `# test
set(TARGET_NAME bb)
set(${TARGET_NAME}_IDE_FOLDER "test")
set(${TARGET_NAME}_DIRS
${BLA_PATH}/src
${BLA_GEN_PATH}
${BLA_GEN_PATH}/dir1/pub
${BLA_GEN_PATH}/dir2/pub
)
set(${TARGET_NAME}_SOURCES
../inc/file3.h
../inc/file4.h
)
rascal>try {
>>>>>>> implode(#AST::Build, t);
>>>>>>>}
>>>>>>>catch IllegalArgument(x, m):
>>>>>>> println(m);
Cannot find a constructor for Build
ok
IllegalArgument异常既有参数也有原因。由于参数太长,它被终端截断,我们无法读取消息:-(消息是Build
找不到与树匹配的抽象构造函数。
让我们看看抽象语法,看看我们是否缺少构造函数,或者它是否存在:
rascal>iprintln(#AST::Build)
type(
adt(
"Build",
[]),
(
adt(
"IdeFolder",
[]): ..., // elided
adt(
"Target",
[]): ..., // elided
adt(
"Build", // bingo!
[]):choice(
adt(
"Build",
[]),
{cons( // this is the constructor it should have found
// data Build = build(list[Section] sections);
label(
"build",
adt(
"Build",
[])),
[label(
"sections",
list(adt(
"Section",
[])))],
[],
{})}),
})
))
ok
看来我们有一个构造函数可以使用。那么发生了什么?我介绍了Java中的implode
代码,发现真正的原因被内部回溯部分中的一个错误隐藏了。现在已经修复了(请参阅更新站点上的下一个连续版本)。
当前更好的错误消息是:
rascal>try { implode(#AST::Build, x); } catch IllegalArgument (t, m) : println("<m> : <t>");
Cannot find a constructor for IdeFolder with name options and arity 3 for syntax type 'Options' : set(${TARGET_NAME}_IDE_FOLDER "test")
因此,这意味着内爆算法已经达到了一个名为options
的语法规则,其类型为Options
,包含3个子节点,同时期望在抽象数据类型中使用IdeFolder。
这意味着数据类型定义中缺少中间级别:
data Section = section(Target target, list[IdeFolder] ideFolders);
可以改为:
data Section = section(Target target, list[Options] ideFolders);
接下来尝试:
try { implode(#AST::Build, x); } catch IllegalArgument (t, m) : println("<m> : <t>");
Cannot find a constructor for Options with name options and arity 3 for syntax type 'Options' : set(${TARGET_NAME}_IDE_FOLDER "test")
现在名字对齐,但是arity没有。我们来解决这个问题:
syntax Options = options: IdeFolder Dirs Sources+;
data Options = options(IdeFolder ideFolder); // has to change to:
data Options = options(IdeFolder ideFolder, Dirs dirs, list[Sources] sources);
我们得到了下一个错误:
rascal>try { implode(#AST::Build, x); } catch IllegalArgument (t, m) : println("<m> : <t>");
Cannot find a constructor for Sources with name sources and arity 3 for syntax type 'Sources' : set(${TARGET_NAME}_SOURCES
../inc/file3.h
../inc/file4.h
)
你现在明白了这个想法:-)你在AST.rsc中还有更多要修复的东西。谢谢你提问。我们刚刚一起变得更好了。