Lexing / Parsing“here”文件

时间:2013-09-09 17:46:02

标签: perl parsing jcl

对于那些是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在这种情况下不是注释...

那么,对此有何看法?我知道会有很多问题澄清我的需求,我很乐意尽可能多地澄清。

提前感谢您的帮助......

2 个答案:

答案 0 :(得分:14)

在这个答案中,我将专注于heredocs,因为课程可以轻松转移到JCL。

任何支持heredocs的语言都不是无上下文的,因此无法使用递归下降等常用技术进行解析。我们需要一种方法来引导词法分析器沿着更加扭曲的路径,但是这样做,我们可以保持无上下文语言的外观。我们所需要的只是另一个堆栈。

对于解析器,我们将heredocs <<END的引入视为字符串文字。但必须扩展词法分析器以执行以下操作:

  • 遇到heredoc介绍时,会将终结符添加到堆栈中。
  • 当遇到换行符时,heredoc的主体是lexed,直到堆栈为空。之后,恢复正常解析。

注意适当更新行号。

在手写的组合解析器/词法分析器中,这可以像这样实现:

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 overviewshort blogpost在Github上使用demo code描述此技术。

答案 1 :(得分:0)

如果有人想知道我是如何决定解决这个问题的,那么我就是这样做的。

我的主要lexing例程接受一个迭代器,它可以提取整行文本(可以从文件,字符串中取出它,无论我想要什么)。例程使用它来创建另一个迭代器,它检查第72列之后的“注释”行,然后它将作为“主线”标记返回,后跟“col72”标记。然后使用这个迭代器来创建另一个迭代器,它将col72标记通过不变的方式传递,但是使用主线标记并将它们变为原子标记(如STRING,NUMBER,COMMA,NEWLINE等)。

但是这里有关键...... lexing例程还有原始ITERATOR ...所以当它收到一个表示有“here”文档的令牌时,它会继续处理令牌,直到它遇到NEWLINE令牌(意味着结束)实际的文本行)然后使用原始迭代器来拉出这里的文档数据。由于该迭代器提供了原子令牌迭代器,因此从中拉出它可以防止这些线被雾化。

为了说明,请考虑像软管这样的迭代器。第一根软管是主要的迭代器。为此,我连接col72迭代器软管,并附上原子标记器软管。随着第一根软管进入角色流,雾化的标记从第三根软管的末端出来。但我可以在第一根软管上安装一个双向喷嘴,使其输出从备用喷嘴流出,防止数据进入第二根软管(因此也就是第三根软管)。当我完成通过备用喷嘴转移数据时,我可以将其关闭,然后数据再次开始流过第二和第三个软管。

易peasey。