perl - Lexing/Parsing "here"文档

标签 perl parsing jcl

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

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

内联数据的布局如下:

//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 并立即为第 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 数据,这些行按原样传递,在这种情况下,第 73-80 列不是注释......

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

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

最佳答案

在这个答案中,我将专注于 heredocs,因为类(class)可以轻松转移到 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通过允许在解析某个标记后触发事件来稍微简化这一点。这些被称为暂停,因为内置的词法分析会暂停片刻以供您接管。这是一个 high-level overview和一个 short blogpost使用 demo code 描述此技术在 Github 上。

    关于perl - Lexing/Parsing "here"文档,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18703999/

    相关文章:

    mysql - 为什么我尝试使用 Perl 的 DBD::mysql 时会出现错误?

    regex - 使用Unicode字符属性时如何模拟单词边界?

    javascript - Node.js:将 markdown 转换为 HTML,然后将特定内容包装在 <section> 标签中

    mainframe - 如何在JCL中使用参数

    select - 如何在卸载作业中为分隔符编写 DB2 SELECT 语句

    mainframe - JCL : IF Statement with SET Statement

    Perl循环不打印出字符

    perl - 什么是 Perl 内置运算符/函数?

    java - 向 Java 解析器/词法分析器发出指令

    java - 解析日期时间字符串与各种浏览器的时区