对于那些是lexing和解析专家的人......我试图在perl中编写一系列程序来解析IBM大型机z / OS JCL用于各种目的,但是它在方法论上遇到了障碍。我主要遵循Mark Jason Dominus在“高阶Perl”中提出的lexing / parsing意识形态,但有些事情我不知道该怎么做。
JCL具有所谓的内联数据,这与“此处”文档非常相似。我不太确定如何把它们变成令牌。
内联数据的布局如下:
//DDNAME DD *
this is the inline data
this is some more inline data
/*
...
传统上,“DD”之后的“*”表示以下行是内联数据本身,由“/ *”或下一个有效JCL记录(前2列中以“//”开头)终止
更高级,内联数据可能如此显示:
//DDNAME DD *,DLM=ZZ
//THIS LOOKS LIKE JCL BUT IT'S ACTUALLY DATA
//MORE DATA MASQUERADING AS JCL
ZZ
...
有时内联数据本身就是JCL(可能是为了程序或内部读者,无论如何)。
但这就是问题。在JCL中,记录是80个字节,长度固定。第72栏(第73-80栏)的所有内容都是“评论”。同样,跟随有效JCL的空白之后的所有内容同样是评论。由于我希望在我的程序中操作JCL并将其吐出来,我想捕获注释以便我可以保留它们。
因此,这是内联数据中内联注释的示例:
//DDNAME DD *,DLM=ZZ THIS IS A COMMENT COL73DAT
data
...
ZZ
...more JCL
我原本以为我可以让我最顶级的词法分析器拉入JCL并立即为第1-72列创建非令牌,然后为第73列创建一个令牌(['COL73COMMENT',$ 1])评论,如果有的话。然后,这将向下游传递给下一个迭代器/标记器,其中包含cols 1-72文本的字符串,后跟col73标记。
但是,我在下游如何获取内联数据?我原本认为最顶级的标记器可能会寻找“DD \ *(,DLM =(\ S *))”(等),然后继续从馈送迭代器中提取记录,直到它达到分隔符为止或有效的JCL启动器(“//”)。
但是你可能会在这里看到问题...我不能拥有2个最顶级的标记符...要么寻找COL73注释的标记生成器必须是顶部,要么获取内联数据的标记生成器必须位于顶部。< / p>
我认为perl解析器有同样的挑战,因为看到了
<<DELIM
不一定是该行的结尾,后面是此处的文档数据。毕竟,你可以看到像perl:
my $this=$obj->ingest(<<DELIM)->reformat();
inline here document data
more data
DELIM
令牌化器/解析器如何知道标记化“) - &gt; reformat();”然后仍按原样获取以下记录?对于内联JCL数据,这些行按原样传递,cols 73-80在这种情况下不是注释...
那么,对此有何看法?我知道会有很多问题澄清我的需求,我很乐意尽可能多地澄清。
提前感谢您的帮助......
答案 0 :(得分:14)
在这个答案中,我将专注于heredocs,因为课程可以轻松转移到JCL。
任何支持heredocs的语言都不是无上下文的,因此无法使用递归下降等常用技术进行解析。我们需要一种方法来引导词法分析器沿着更加扭曲的路径,但是这样做,我们可以保持无上下文语言的外观。我们所需要的只是另一个堆栈。
对于解析器,我们将heredocs <<END
的引入视为字符串文字。但必须扩展词法分析器以执行以下操作:
注意适当更新行号。
在手写的组合解析器/词法分析器中,这可以像这样实现:
use strict; use warnings; use 5.010;
my $s = <<'INPUT-END'; pos($s) = 0;
<<A <<B
body 1
A
body 2
B
<<C
body 3
C
INPUT-END
my @strs;
push @strs, parse_line() while pos($s) < length($s);
for my $i (0 .. $#strs) {
say "STRING $i:";
say $strs[$i];
}
sub parse_line {
my @strings;
my @heredocs;
$s =~ /\G\s+/gc;
# get the markers
while ($s =~ /\G<<(\w+)/gc) {
push @strings, '';
push @heredocs, [ \$strings[-1], $1 ];
$s =~ /\G[^\S\n]+/gc; # spaces that are no newlines
}
# lex the EOL
$s =~ /\G\n/gc or die "Newline expected";
# process the deferred heredocs:
while (my $heredoc = shift @heredocs) {
my ($placeholder, $marker) = @$heredoc;
$s =~ /\G(.*\n)$marker\n/sgc or die "Heredoc <<$marker expected";
$$placeholder = $1;
}
return @strings;
}
输出:
STRING 0:
body 1
STRING 1:
body 2
STRING 2:
body 3
Marpa parser通过允许在解析某个令牌后触发事件来简化这一点。这些被称为暂停,因为内置的lexing暂停片刻让你接管。以下是high-level overview和short blogpost在Github上使用demo code描述此技术。
答案 1 :(得分:0)
如果有人想知道我是如何决定解决这个问题的,那么我就是这样做的。
我的主要lexing例程接受一个迭代器,它可以提取整行文本(可以从文件,字符串中取出它,无论我想要什么)。例程使用它来创建另一个迭代器,它检查第72列之后的“注释”行,然后它将作为“主线”标记返回,后跟“col72”标记。然后使用这个迭代器来创建另一个迭代器,它将col72标记通过不变的方式传递,但是使用主线标记并将它们变为原子标记(如STRING,NUMBER,COMMA,NEWLINE等)。
但是这里有关键...... lexing例程还有原始ITERATOR ...所以当它收到一个表示有“here”文档的令牌时,它会继续处理令牌,直到它遇到NEWLINE令牌(意味着结束)实际的文本行)然后使用原始迭代器来拉出这里的文档数据。由于该迭代器提供了原子令牌迭代器,因此从中拉出它可以防止这些线被雾化。
为了说明,请考虑像软管这样的迭代器。第一根软管是主要的迭代器。为此,我连接col72迭代器软管,并附上原子标记器软管。随着第一根软管进入角色流,雾化的标记从第三根软管的末端出来。但我可以在第一根软管上安装一个双向喷嘴,使其输出从备用喷嘴流出,防止数据进入第二根软管(因此也就是第三根软管)。当我完成通过备用喷嘴转移数据时,我可以将其关闭,然后数据再次开始流过第二和第三个软管。
易peasey。