对于那些是词法分析专家的人来说...我试图用perl编写一系列程序,这些程序可以出于各种目的解析出IBM大型机z / OS JCL,但是却在方法论上遇到了障碍。我主要遵循Mark Jason Dominus在“高级Perl”中提出的词汇化/解析思想,但是有些事情我还不太清楚该怎么做。
JCL具有所谓的内联数据,与“这里”文档非常相似。我不太确定如何将这些词分解成令牌。
内联数据的布局如下:
//DDNAME DD *
this is the inline data
this is some more inline data
/*
...
按照惯例,“ DD”之后的“ *”表示以下行是内联数据本身,以“ / *”或下一个有效的JCL记录(在前两列中以“ //”开头)结尾。
更高级的是,内联数据可能如下所示:
//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行,并立即为cols 1-72创建一个非令牌,然后为第73列注释创建一个令牌(['COL73COMMENT',$ 1]),如果任何。然后,这会将一串cols 1-72文本后跟col73令牌向下游传递到下一个迭代器/令牌器。
但是,我将如何从那里下游获取内联数据?最初,我认为最顶端的标记化程序可以寻找“ DD \ *(,DLM =(\ S *))”(或类似名称),然后继续从提要迭代器中提取记录,直到到达定界符为止或有效的JCL启动器(“ //”)。
但是您可能会在这里看到问题...我不能有2个最顶部的标记生成器...寻找COL73注释的标记生成器必须位于顶部,或者获取内联数据的标记生成器必须位于顶部。
我想自从看到Perl解析器以来,同样面临挑战
<<DELIM
不一定是行的末尾,其后是here文档数据。毕竟,您可能会看到像以下的perl:
my $this=$obj->ingest(<<DELIM)->reformat();
inline here document data
more data
DELIM
分词器/解析器如何知道对“)-> reformat();”进行分词。然后仍按原样获取以下记录?对于内联JCL数据,这些行将按原样传递,在这种情况下,cols 73-80不是注释...
那么,对此有何建议?我知道会有很多问题可以阐明我的需求,并且很高兴根据需要澄清所有问题。
预先感谢您的任何帮助...
最佳答案
在这个答案中,我将专注于heredocs,因为这些课程可以轻松地转移到JCL。
任何支持heredocs的语言都不是上下文无关的,因此无法使用诸如递归下降之类的通用技术进行解析。我们需要一种方法来引导词法分析器沿更复杂的路径前进,但是这样做可以保持上下文无关语言的外观。我们需要的是另一个堆栈。
对于解析器,我们将heredocs <<END
的介绍视为字符串文字。但是词法分析器必须扩展为执行以下操作:
遇到Heredoc简介时,它将终止符添加到堆栈中。
遇到换行符时,heredoc的正文将被词法化,直到堆栈为空。之后,恢复正常解析。
请注意适当地更新行号。
在手写的组合解析器/词法分析器中,可以这样实现:
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通过允许在解析特定令牌后触发事件来简化此操作。这些称为暂停,因为内置词法暂停片刻让您接管。这是在Github上使用high-level overview描述此技术的short blogpost和demo code。