Antlr 4: A way to switch modes in a parser

I am trying to build a MVS JCL resolver using Antlr4. The overall effort is going well enough, but I'm having trouble using the MVS equivalent of the * nix "here docs" (embedded files). I cannot use lexer modes for flip-flop between JCL and content here-doc, so I am looking for alternatives that can use parser layer.

IBM MVS allows for "incremental datasets" similar to * nix here-docs.

Example:

This defines a three-line inline file, terminated with "ZZ" characters, and available to the help program using the "ANYNAME" label:

//ANYNAME  DD *,SYMBOLS=(JCLONLY,FILEREF),DLM=ZZ
HEREDOC TEXT 1
HEREDOC TEXT 2
HEREDOC TEXT 3
ZZ
//NEXTFILE DD ...stuff...

      

ANYNAME

is a handle with which the program can access the content of here-doc.

DD *

is required and tells MVS that the following document follows.

SYMBOLS=(JCLONLY,FILEREF)

- more information on how the here-doc is handled.

DLM=ZZ

is also optional and defines a here-doc terminator (by default terminator = /*

).

I need to be able to parse the string at the parser level //ANYNAME...

(I have this bit) and then read the contents of the here-doc until I find here (maybe not the default) doc terminator . In a sense, this is similar to the lexer mode capability, but at the moment I'm working in a parser and I don't have a fixed terminator to work with.

I need a mode switching guide to process my here-document and then come back again to continue processing my JCL.

Below is an extremely abbreviated version of my grammar (the actual grammar is still around 2,200 lines and is incomplete).

Thanks for any ideas. I appreciate your help, comments and suggestions.

/* the ddstmt parser rule should be considered the main entry point. It handles (at least):

           //ANYNAME  DD *,SYMBOLS=(JCLONLY,FILEREF),DLM=ZZ
    and    //         DD *,DLM=ZZ
    and    //ANYNAME  DD *,SYMBOLS=EXECSYS
    and    //ANYNAME  DD *

  I need to be able process the above line as JCL then read the here-doc content...

                   "HEREDOC TEXT 1"
                   "HEREDOC TEXT 2"
                   "HEREDOC TEXT 3"

  as either a single token or a series of tokens, then, after reading the here-doc 
  delimiter...

                   "ZZ"

 , go back to processing regular JCL again.

*/


    /* lexer rules: */

                LINECOMMENT3        :   SLASH SLASH STAR                            ;
                DSLASH              :   SLASH SLASH                                 ;
                INSTREAMTERMINATE   :   SLASH STAR                                  ;
                SLASH               :   '/'                                         ;
                STAR                :   '*'                                         ;
                OPAREN              :   '('                                         ;
                CPAREN              :   ')'                                         ;
                COMMA               :   ','                                         ;

                KWDD                :   'DD'                                        ;
                KWDLM               :   'DLM'                                       ;
                KWSYMBOLS           :   'SYMBOLS'                                   ;
                KWDATA              :   'DATA'                                      ;

                SYMBOLSTARGET       :   'JCLONLY'|'EXECSYS'|'CNVTSYS'               ;
                EQ                  :   '='                                         ;
                APOST               :   '\''                                        ;
                fragment
                SPC                 :   ' '                                         ;
                SPCS                :   SPC+                                        ;
                NL                  :   ('\r'? '\n')                                ;
                UNQUOTEDTEXT        :   (APOST APOST|~[=\'\"\r\n\t,/() ])+          ;


    /* parser rules: */

                label               :   unquotedtext
                                    ;
                separator           :   SPCS
                                    ;

        /* handle crazy JCL comment rules - start */
                    partcomment         :   SPCS partcommenttext NL
                                        ;
                    partcommenttext     :   ((~NL+?)?)
                                        ;
                    linecomment         :   LINECOMMENT3 linecommenttext NL
                                        ;
                    linecommenttext     :   ((~NL+?)?)
                                        ;
                    postcommaeol        :   ( (partcomment|NL) linecomment* DSLASH SPCS )?
                                        ;
                    poststmteol         :   ( (partcomment|NL) linecomment* )?
                                        ;
        /* handle crazy JCL comment rules - end */

                ddstmt              :   DSLASH (label|) separator KWDD separator dddecl
                                    ;
                dddecl              :   ...
                                    |   ddinstreamdecl
                                    |   ...
                                    ;
                ddinstreamdecl      :   (STAR|KWDATA) poststmteol ddinstreamopts
                                    ;
                ddinstreamopts      :    ( COMMA postcommaeol ddinstreamopt poststmteol )*
                                    ;
                ddinstreamopt       :    (   ddinstreamdelim
                                         |   symbolsdecl
                                         )
                                    ;
                ddinstreamdelim     :   KWDLM EQ unquotedtext
                                    ;
                symbolsdecl         :   KWSYMBOLS EQ symbolsdef
                                    ;
                symbolsdef          :   OPAREN symbolstarget ( COMMA symbolsloggingdd )? CPAREN
                                    |   symbolstarget
                                    ;
                symbolstarget       :   SYMBOLSTARGET
                                    ;
                symbolsloggingdd    :   unquotedtext
                                    ;
                unquotedtext        :   UNQUOTEDTEXT
                                    ;

      

+3


source to share


1 answer


Your lexer must be able to mark the entire document before starting the parsing operation. Any attempt to control a lexer from a parser is a recipe for endless nightmares along the way. The following PHP Lexer snippets show how predicates can be used in conjunction with lexer modes to detect the end of a string with a user-defined delimiter.The key part is to write the start delimiter and then validate tokens that start at the beginning of the string against it.

PHP_NOWDOC_START
    :   '<<<\'' PHP_IDENTIFIER '\'' {_input.La(1) == '\r' || _input.La(1) == '\n'}?
        -> pushMode(PhpNowDoc)
    ;

mode PhpNowDoc;

    PhpNowDoc_NEWLINE : NEWLINE -> type(NEWLINE);

    PHP_NOWDOC_END
        :   {_input.La(-1) == '\n'}?
            PHP_IDENTIFIER ';'?
            {CheckHeredocEnd(_input.La(1), Text);}?
            -> popMode
        ;

    PHP_NOWDOC_TEXT
        :   ~[\r\n]+
        ;

      



The ID is actually written in a custom override NextToken()

(shown here for C # target):

public override IToken NextToken()
{
    IToken token = base.NextToken();
    switch (token.Type)
    {
    case PHP_NOWDOC_START:
        // <<<'identifier'
        _heredocIdentifier = token.Text.Substring(3).Trim('\'');
        break;

    case PHP_NOWDOC_END:
        _heredocIdentifier = null;
        break;

    default:
        break;
    }

    return token;
}

private bool CheckHeredocEnd(int la1, string text)
{
    // identifier
    //  - or -
    // identifier;
    bool semi = text[text.Length - 1] == ';';
    string identifier = semi ? text.Substring(0, text.Length - 1) : text;
    return string.Equals(identifier, HeredocIdentifier, StringComparison.Ordinal);
}

      

+3


source







All Articles