15

对于那些在词法分析和解析方面的专家......我正在尝试用 perl 编写一系列程序,这些程序将解析 IBM 大型机 z/OS JCL 用于各种目的,但在方法论上遇到了障碍。我主要遵循 Mark Jason Dominus 在“Higher Order Perl”中提出的词法/解析思想,但有些事情我不太清楚该怎么做。

JCL 有所谓的内联数据,它与“here”文档非常相似。我不太确定如何将这些转换为令牌。

内联数据的布局如下:

//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 并立即为 cols 1-72 创建一个非标记,然后为第 73 列评论创建一个标记 (['COL73COMMENT',$1]),如果任何。然后,这会将 cols 1-72 文本后跟 col73 令牌的字符串向下传递给下一个迭代器/标记器。

但是,我将如何从那里获取内联数据?我最初认为最顶层的标记器可以查找“DD \*(,DLM=(\S*))”(或类似的),然后继续从馈送迭代器中提取记录,直到它到达分隔符或有效的 JCL 启动器 ("//")。

但是您可能会在这里看到问题……我不能有 2 个最顶层的标记器……查找 COL73 注释的标记器必须位于顶部,或者获取内联数据的标记器必须位于顶部。

我想 perl 解析器也有同样的挑战,因为看到

<<DELIM

不一定是行尾,后面是这里的文档数据。毕竟,你可以看到 perl 像:

my $this=$obj->ingest(<<DELIM)->reformat();
inline here document data
more data
DELIM

标记器/解析器如何知道标记“)->reformat();” 然后仍然按原样抓取以下记录?对于内联 JCL 数据,这些行按原样传递,在这种情况下,第 73-80 列不是注释...

那么,有没有人接受这个?我知道会有很多问题澄清我的需求,我很乐意尽可能多地澄清。

在此先感谢您的帮助...

4

2 回答 2

14

在这个答案中,我将专注于 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 解析器通过允许在解析某个标记后触发事件来简化这一点。这些被称为pauses,因为内置的 lexing 会暂停片刻让您接管。这是一个高级概述和一篇简短的博文,用Github 上的演示代码描述了这种技术。

于 2013-09-09T19:16:03.587 回答
0

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

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

但这是症结所在...... lexing 例程仍然有 ORIGINAL ITERATOR...... 所以当它收到一个指示存在“here”文档的标记时,它会继续处理标记,直到它遇到一个 NEWLINE 标记(意味着实际结束文本行),然后使用原始迭代器提取此处的文档数据。由于该迭代器提供原子标记迭代器,因此从中拉出然后防止这些行被原子化。

举例来说,想想像软管这样的迭代器。第一个软管是主要的迭代器。为此,我连接了 col72 迭代器软管,并连接了 atomic tokenizer 软管。随着字符流进入第一根软管,雾化标记从第三根软管的末端出来。但是我可以将一个双向喷嘴连接到第一个软管,这将允许其输出从备用喷嘴出来,防止数据进入第二个软管(以及第三个软管)。当我通过备用喷嘴完成数据转移后,我可以将其关闭,然后数据再次开始流经第二和第三根软管。

轻松愉快。

于 2013-09-18T15:08:38.770 回答