[jabascript]EBNF

BNFがまぁまぁできたので、EBNFにチャレンジ。

syntax = syntax_rule { "|" syntax_rule } ;
syntax_rule = identifier "=" definitions_list ";" ;
definitions_list = single_definition { "|" single_definition } ;
single_definition = term { term } ;
term = [ repetition ] ( group | option | literal | nonterminal ) ;
repetition = "{" | "}" | "[" | "]" ;
group = "(" definitions_list ")" ;
option = "[" definitions_list "]" ;
literal = "'" character "'" | '"' character '"' ;
nonterminal = identifier ;
identifier = letter { letter | digit | "_" } ;
letter = "A" | "B" | ... | "Z" | "a" | "b" | ... | "z" ;
digit = "0" | "1" | ... | "9" ;
character = letter | digit | symbol ;
symbol = "$" | "&" | "#" | "@" | "%" | "+" | "-" | "/" | "*" | "!" | "?" | "<" | ">" | "=" | ":" | "~" | "^" | "." | "|" ;

そのまま、コード化してもうまくいかず・・・

(* 文法   *)
syntax      =   rule_list /\s*/ ;
rule_list   =   { ( rule | comment ) }+;
rule        =   identifier ( "=" | "::=" ) definitions ";";
(* 定義   *)
definitions =   definition { "|" definition }* ;                (* 定義リスト *)
definition  =   { ( list | wrap | word ) }+ ;                   (* 定義 *)
(* リスト *)
list        =   choice | sequnce ;                              (* 二項演算子でwrapをr繋いだリスト *)
choice      =   ( wrap | word ) { "|" ( wrap | word ) }* ;      (* アレかコレかソレかのどれか *)
sequnce     =   ( wrap | word ) { [ "," ] ( wrap | word ) }* ;  (* アレとコレとソレの一式。","は空白改行と同義  *)
(* 囲み   *)
wrap        =   ( option | group | repeate );
option      =   "[" choice "]" ;                                (* あれば尚良し *)
group       =   "(" choice ")" ;                                (* choiceを明示的に括る表現 *)
repeate     =   "{" sequnce "}" [ "*" | "+" ] ;                 (* 0回以上繰り返す *: 0回以上、+:1回以上 *)
(* 語彙   *)
word        =   ( wq_string | sq_string | regexp | identifier ) ;       
wq_string   =   '"' /(?!\\)[^"]*/ '"' ;                               (* " で括られたテキスト *)
sq_string   =   "'" /(?!\\)[^']*/ "'" ;                               (* ' で括られたテキスト *)
regexp      =   "/" /.+/ "/" ;                                  (* 正規表現されたテキスト *)
identifier  =   /[A-Za-z][A-Za-z0-9_]*/ ;                       (* 名称 *)
comment     =   /[(][*](.*)[*][)]/ ;                            (* 注意書き *)

に変更した。

ちょっと使いにくいのが

repeate     =   "{" sequnce "}" { repeate_opt } ; 

で、例えば、

{ a | b } aかbがあれば良いなぁ~
{ a b }  a b と並ぶと良いなぁ~

どちらも使えたら良いと思えるけど、パーサジェネレータにコードする際に

//  repeate     =   "{" sequnce "}" { repeate_opt } ;
const repeate = _lazy(() => {
    return _map(
        "{", sequnce, "}", _option(repeate_opt)
        , (parsed) => {
            return { rule: parsed };
        })
});

な感じでEBNF文法を取り込む時に

{ a, b, c, d } ⇒ _repeate( a, b, c, d )

のパラメータ部をsequnceとして解釈すれば、ほぼ書き直さずに済むけど

{ a | b | c | d } ⇒ _repeate( a | b | c | d )

のパラメータ部をgroupとして解釈できなくもないが、a b c dの戻り値がboolean型に制限される。

つまり、パーサコンビネータが{ true: 成功、false: 失敗 }しか返せなくなってしまう。

文法の解釈した結果を外部変数にスタックし自分が読込んだ位置を保持すれば済む話ではあるけれど、面倒この上無いし、そこまで整えるくならLL法のパーサライブラリィを使えば良い気がする。

{
   "_syntax": {
     "_rule_list": [
       { "_comment": "(* 文法  *)" },
       {
         "syntax": {
           "_definition": {
             "_sequence": [
               { "_identifier": "rule_list" },
               { "_regexp": "/s*/" }
             ]
           }
         }
       },
       {
         "rule_list": {
           "_definition": {
             "_repeate1": {
               "_group": [
                 { "_identifier": "rule" },
                 { "_identifier": "comment" }
               ]
             }
           }
         }
       },
       {
         "rule": {
           "_definition": {
             "_sequence": [
               { "_identifier": "identifier" },
               {
                 "_group": [
                   { "_wq_string": "=" },
                   { "_wq_string": "::=" }
                 ]
               },
               { "_identifier": "definitions" },
               { "_wq_string": ";" }
             ]
           }
         }
       },
       { "_comment": "(* 定義  *)" },
       {
         "definitions": {
           "_definition": {
             "_sequence": [
               { "_identifier": "definition" },
               {
                 "_repeate0": {
                   "_sequence": [
                     { "_wq_string": "|" },
                     { "_identifier": "definition" }
                   ]
                 }
               }
             ]
           }
         }
       },
       { "_comment": "(* 定義リスト *)" },
       {
         "definition": {
           "_definition": {
             "_repeate1": {
               "_group": [
                 { "_identifier": "list" },
                 { "_identifier": "wrap" },
                 { "_identifier": "word" }
               ]
             }
           }
         }
       },
       { "_comment": "(* 定義 *)" },
       { "_comment": "(* リスト *)" },
       {
         "list": {
           "_definition": {
             "_choice": [
               { "_identifier": "choice" },
               { "_identifier": "sequnce" }
             ]
           }
         }
       },
       { "_comment": "(* 二項演算子でwrapをr繋いだリスト *)" },
       {
         "choice": {
           "_definition": {
             "_sequence": [
               {
                 "_group": [
                   { "_identifier": "wrap" },
                   { "_identifier": "word" }
                 ]
               },
               {
                 "_repeate0": {
                   "_sequence": [
                     { "_wq_string": "|" },
                     {
                       "_group": [
                         { "_identifier": "wrap" },
                         { "_identifier": "word" }
                       ]
                     }
                   ]
                 }
               }
             ]
           }
         }
       },
       { "_comment": "(* アレかコレかソレかのどれか *)" },
       {
         "sequnce": {
           "_definition": {
             "_sequence": [
               {
                 "_group": [
                   { "_identifier": "wrap" },
                   { "_identifier": "word" }
                 ]
               },
               {
                 "_repeate0": {
                   "_sequence": [
                     {
                       "_option": { "_wq_string": "," }
                     },
                     {
                       "_group": [
                         { "_identifier": "wrap" },
                         { "_identifier": "word" }
                       ]
                     }
                   ]
                 }
               }
             ]
           }
         }
       },
       { "_comment": "(* アレとコレとソレの一式。\",\"は空白改行と同義 *)" },
       { "_comment": "(* 囲み  *)" },
       {
         "wrap": {
           "_definition": {
             "_group": [
               { "_identifier": "option" },
               { "_identifier": "group" },
               { "_identifier": "repeate" }
             ]
           }
         }
       },
       {
         "option": {
           "_definition": {
             "_sequence": [
               { "_wq_string": "[" },
               { "_identifier": "choice" },
               { "_wq_string": "]" }
             ]
           }
         }
       },
       { "_comment": "(* あれば尚良し *)" },
       {
         "group": {
           "_definition": {
             "_sequence": [
               { "_wq_string": "(" },
               { "_identifier": "choice" },
               { "_wq_string": ")" }
             ]
           }
         }
       },
       { "_comment": "(* choiceを明示的に括る表現 *)" },
       {
         "repeate": {
           "_definition": {
             "_sequence": [
               { "_wq_string": "{" },
               { "_identifier": "sequnce" },
               { "_wq_string": "}" },
               {
                 "_option": {
                   "_choice": [
                     { "_wq_string": "*" },
                     { "_wq_string": "+" }
                   ]
                 }
               }
             ]
           }
         }
       },
       { "_comment": "(* 0回以上繰り返す *: 0回以上、+:1回以上 *)" },
       { "_comment": "(* 語彙  *)" },
       {
         "word": {
           "_definition": {
             "_group": [
               { "_identifier": "wq_string" },
               { "_identifier": "sq_string" },
               { "_identifier": "regexp" },
               { "_identifier": "identifier" }
             ]
           }
         }
       },
       {
         "wq_string": {
           "_definition": {
             "_sequence": [
               { "_sq_string": "\"" },
               { "_regexp": "/(?!\\)[^\"]*/" },
               { "_sq_string": "\"" }
             ]
           }
         }
       },
       { "_comment": "(* \" で括られたテキスト *)" },
       {
         "sq_string": {
           "_definition": {
             "_sequence": [
               { "_wq_string": "'" },
               { "_regexp": "/(?!\\)[^']*/" },
               { "_wq_string": "'" }
             ]
           }
         }
       },
       { "_comment": "(* ' で括られたテキスト *)" },
       {
         "regexp": {
           "_definition": {
             "_sequence": [
               { "_wq_string": "/" },
               { "_regexp": "/.+/" },
               { "_wq_string": "/" },
               {
                 "_repeate0": { "_regexp": "/[dgimsuvy]/" }
               }
             ]
           }
         }
       },
       { "_comment": "(* 正規表現されたテキスト *)" },
       {
         "identifier": {
           "_definition": { "_regexp": "/[A-Za-z][A-Za-z0-9_]*/" }
         }
       },
       { "_comment": "(* 名称 *)" },
       {
         "comment": {
           "_definition": { "_regexp": "/[(][*](.*)[*][)]/" }
         }
       },
       { "_comment": "(* 注意書き *)" }
     ]
   }
 }
 

と結果が得られたので良し。エディタを使って短くするのが面倒になってきたので

let json = JSON.stringify(list, null, 2);
// 特定のデータを一行に纏める呪文
json = json.replaceAll(/[{]\s*"(_identifier|_regexp|_wq_string|_sq_string|_comment)":\s*(".*")\s*[}]/g, '{ "$1": $2 }');
console.log(json);

一行呪文を生成した。やはり、【メンドクサイ】はプログラミングの母である。

class EBNF {
    /**
     *  EBNFルールと文法を作成
     * @param {string} ebnf      BNFテキスト
     * @param {function} proc   mapのproc
     * @returns mapまたはbnfパターンの戻り値
     */
    parseEBNF2(ebnf, proc) {
        const _map = this.map.bind(this);
        const _sequence = this.sequence.bind(this);
        const _option = this.option.bind(this);
        const _group = this.choice.bind(this);
        const _choice = this.choice.bind(this);
        const _repeate = this.repeate.bind(this);
        const _lazy = this.lazy.bind(this);
        //  sequnce, choice, repeateのパラメータのparser部に差し込んで使用する
        const _debugPrint = (target, position) => {
            const text = this.positionText(target, position);
            console.log(`_trap: '${text}'`);
            return undefined;
        };
        const _getValue = (a) => {
            const keys = Object.keys(a);
            return a[keys[0]];
        }
        //  comment     =   "(*" /(?![*][)])(.*)/ ;
        const comment = _map(
            /[(][*](.*)[*][)]/
            , (parsed) => {
                return { _comment: parsed };
            });
        //  identifier  =   /[A-Za-z][A-Za-z0-9_]*/ ;
        const identifier = _map(
            /[A-Za-z][A-Za-z0-9_]*/
            , (parsed) => {
                return { _identifier: parsed };
            });
        //  regexp      =   "/" /(?!\\)[^/]+/ "/" ;
        const regexp = _map(
            "/", /(?!\\)[^/]+/, "/"
            , (parsed) => {
                return { _regexp: `/${parsed[1]}/` };
            });
        //  sq_string   =   "'" /(?!\\)[^"]*/ "'" ;
        const sq_string = _map(
            "'", /(?!\\)[^']*/, "'"
            , (parsed) => {
                return { _sq_string: parsed[1] };
            });
        //  wq_string   =   '"' /(?!\\)[^"]*/ '"' ;
        const wq_string = _map(
            '"', /(?!\\)[^"]*/, '"'
            , (parsed) => {
                return { _wq_string: parsed[1] };
            });
        //  word        =   wq_string | sq_string | regexp | identifier ;
        const word = _map(
            _choice(wq_string, sq_string, regexp, identifier)
            , (parsed) => {
                return parsed;
            });
        //  repeate     =   "{" sequnce "}" [ "*" | "+" ] ;
        const repeate = _lazy(() => {
            return _map(
                "{", sequnce, "}", _option("*", "+")
                , (parsed) => {
                    const lparen = parsed[0];
                    const seq = parsed[1];
                    const rparen = parsed[2];
                    const opt = parsed[3];
                    return (opt === '+') ? { _repeate1: seq } : { _repeate0: seq };
                })
        });
        //  group       =   "(" choice ")" ;
        const group = _lazy(() => {
            return _map(
                "(", choice, ")"
                , (parsed) => {
                    return { _group: parsed[1]._choice };
                })
        });
        //  option      =   "[" choice "]" ;
        const option = _lazy(() => {
            return _map(
                "[", choice, "]"
                , (parsed) => {
                    return { _option: parsed[1] };
                })
        });
        // wrap        =   ( option | group | repeate ) ;
        const wrap = _map(
            _group(option, group, repeate)
            , (parsed) => {
                if (Array.isArray(parsed)) {
                    if (parsed.length === 1) return parsed[0];
                    return { _wrap: parsed };
                }
                return parsed;
            });
        //  sequnce     =   ( wrap | word ) { [ "," ] ( wrap | word ) }* ;
        const sequnce = _map(
            _group(wrap, word), _repeate(_option(","), _group(wrap, word), 0)
            , (parsed) => {
                if (Array.isArray(parsed)) {
                    parsed = parsed.filter((p) => !(Array.isArray(p) && p.length === 0));
                    const stack = [];
                    let p1 = parsed.concat();
                    while (0 < p1.length) {
                        const p2 = p1.shift();
                        if (Array.isArray(p2)) {
                            p1 = p2.concat();
                        } else {
                            if (p2 != null) {
                                stack.push(p2);
                            }
                        }
                    }
                    //  配列要素が1個の場合はその内容を返す
                    if (stack.length === 1) {
                        return stack[0]
                    }
                    return { _sequence: stack };
                }
                return parsed;
            });
        //  choice      =   ( wrap | word ) { "|" ( wrap | word ) }* ;
        const choice = _map(
            _group(wrap, word), _repeate("|", _group(wrap, word), 0)
            , (parsed) => {
                if (Array.isArray(parsed)) {
                    parsed = parsed.filter((p) => !(Array.isArray(p) && p.length === 0));
                    if (parsed.length === 1) return parsed[0];
                    const stack = [];
                    const p1 = parsed.shift();
                    stack.push(p1);
                    let p2 = parsed.shift();
                    while (0 < p2.length) {
                        const p2a = p2.shift();
                        const p2aa = p2a.shift();
                        const p2ab = p2a.shift();
                        stack.push(p2ab);
                    }
                    return { _choice: stack };
                }
                return parsed;
            });
        //  list        =   choice | sequnce ;
        const list = _map(
            _choice(choice, sequnce)
            , (parsed) => {
                if (Array.isArray(parsed)) {
                    if (parsed.length === 1) return parsed[0];
                    return { _list: parsed };
                }
                return parsed;
            });
        //  definition  =   { ( list | wrap | word ) }+ ;
        const definition = _map(
            _repeate(_group(list, wrap, word), 1)
            , (parsed) => {
                if (Array.isArray(parsed)) {
                    //  _repeateが[]を返すっぽい
                    parsed = parsed.filter((p) => !(Array.isArray(p) && p.length === 0));
                    if (parsed.length === 1) return { _definition: parsed[0] };
                    return { _definition: { _sequence: parsed } };
                }
                return { _definition: parsed };
            });
        //  definitions =   definition { "|" definition }* ;
        const definitions = _map(
            definition, _repeate("|", definition, 0)
            , (parsed) => {
                if (Array.isArray(parsed)) {
                    //  _repeateが[]を返すっぽい
                    parsed = parsed.filter((p) => !(Array.isArray(p) && p.length === 0));
                }
                parsed.map((p) => p._definition);
                return parsed[0];
            });
        //  rule        =   identifier ( "=" | "::=" ) definitions ";";
        const rule = _map(
            identifier, _group("=", "::="), definitions, ";"
            , (parsed) => {
                const name = parsed[0]._identifier;
                const eq = parsed[1];
                const definitions = parsed[2];
                const semicolon = parsed[3];
                const a = {};
                a[name] = definitions;
                return a;
            });
        //  rule_list   =   { ( rule | comment ) }+ ;
        const rule_list = _map(
            _repeate(_group(rule, comment), 1)
            , (parsed) => {
                if (Array.isArray(parsed)) {
                    //  _repeateが[]を返すっぽい
                    parsed = parsed.filter((p) => !(Array.isArray(p) && p.length === 0));
                }
                return { _rule_list: parsed };
            });
        //  syntax      =   rule_list /\s*/ ;
        const syntax = _map(
            rule_list, /\s*/
            , (parsed) => {
                return { _syntax: parsed[0] };
            });
        //
        //  読取り位置の空白読み飛ばしフラグ
        this.fSpaceSkip = true;
        const result = syntax(ebnf, 0);
        if (!result.success) {
            console.log('パース失敗');
        } else if (ebnf.length !== result.position) {
            const text = this.positionText(ebnf, result.position);
            console.log(`${ebnf.length}文字中の${1 + result.position}文字目 '${text}' でパース失敗`);
        }
        if (proc) {
            return proc(result);
        }
        return result.result;
    }
    /**
     * 
     */
    testParseEBNF2() {
        const ebnf = `
            (* 文法   *)
            syntax      =   rule_list /\s*/ ;
            rule_list   =   { ( rule | comment ) }+;
            rule        =   identifier ( "=" | "::=" ) definitions ";";
            (* 定義   *)
            definitions =   definition { "|" definition }* ;                (* 定義リスト *)
            definition  =   { ( list | wrap | word ) }+ ;                   (* 定義 *)
            (* リスト *)
            list        =   choice | sequnce ;                              (* 二項演算子でwrapをr繋いだリスト *)
            choice      =   ( wrap | word ) { "|" ( wrap | word ) }* ;      (* アレかコレかソレかのどれか *)
            sequnce     =   ( wrap | word ) { [ "," ] ( wrap | word ) }* ;  (* アレとコレとソレの一式。","は空白改行と同義  *)
            (* 囲み   *)
            wrap        =   ( option | group | repeate );
            option      =   "[" choice "]" ;                                (* あれば尚良し *)
            group       =   "(" choice ")" ;                                (* choiceを明示的に括る表現 *)
            repeate     =   "{" sequnce "}" [ "*" | "+" ] ;                 (* 0回以上繰り返す *: 0回以上、+:1回以上 *)
            (* 語彙   *)
            word        =   ( wq_string | sq_string | regexp | identifier ) ;       
            wq_string   =   '"' /(?!\\)[^"]*/ '"' ;                         (* " で括られたテキスト *)
            sq_string   =   "'" /(?!\\)[^']*/ "'" ;                         (* ' で括られたテキスト *)
            regexp      =   "/" /.+/  "/" { /[dgimsuvy]/ } ;                (* 正規表現されたテキスト *)
            identifier  =   /[A-Za-z][A-Za-z0-9_]*/ ;                       (* 名称 *)
            comment     =   /[(][*](.*)[*][)]/ ;                            (* 注意書き *)
`;
        this.parseEBNF2(ebnf, (parsed) => {
            const list = parsed.result;
            let json = JSON.stringify(list, null, 2);
            // 特定のデータを一行に纏める呪文
            json = json.replaceAll(/[{]\s*"(_identifier|_regexp|_wq_string|_sq_string|_comment)":\s*(".*")\s*[}]/g, '{ "$1": $2 }');
            console.log(json);
            // list._syntax._rule_list.forEach((v) => {
            //     if (v._comment === undefined) {
            //         const json = JSON.stringify(v, null, 2) + ',';
            //         console.log(json);
            //     }
            // });
        });

パーサの結果を見ると意味が重複していたり、

_definition: [
"aaa",
・・・
"zzz",
]

と、_definition直下の配列は_choice?_sequence?どっちか悩みそうなので_sequenceの配下した等、結構内容を調整している。但し、_sequenceと_choiceは同じ様に_repeateの結果をフラットにするハズだったが他の結果の調整の差異で食い違が出てしまった。_wq_stringや_sq_stringがparsed[1]を返すのはJSON.stringifyでテキスト化した時に読みにくいからで、色々実装したらparsed.join(”)になるかもしれない。そして、組込みパーサもEBNF用パーサを書きやすくするために改造したのでスプレッドシートには使えないかもしれない。

    /**
     * 
     * @param {*} target 
     * @param {*} position 
     * @returns 
     */
    positionText(target, position, offset = 20) {
        const text = target.substring(position - offset, position)
            + '[' + target.substring(position, position + 1) + ']'
            + target.substring(position + 1, position + offset)
        return text;
    }
・・・中略・・・
    /**
     *  読取り位置の空白の読飛ばし
     * @param {string} target 
     * @param {integer} position 
     * @returns {Array} [target, position]
     */
    skipSpace(target, position) {
        const result = (/^\s*/).exec(target.substring(position));
        if (result) {
            const len = result[0].length;
            return [target, position + len];
        }
        return [target, position];
    }
    /**
     * 指定テキストのパーサ(関数)を作成する
     * @param {string} text         指定テキスト
     * @return {function}           生成した指定テキストのパーサ
     */
    token(text) {
        const methodName = 'token';
        const len = text.length;
        const func =
            /**
             *  生成した指定テキストのパーサ
             * @param {string} target       ソースコード
             * @param {integer} position    読取り位置
             * @return {ParseInfo}             パースした結果
             */
            (target, position) => {/*token*/
                if (this.fSpaceSkip) { [target, position] = this.skipSpace(target, position); }
                if (typeof target !== 'string') {
                    let a = 1;
                }
                if (target.substring(position, position + len) === text) {
                    //  読取りに成功した場合
                    return new ParseInfo(true, text, position + len);
                } else {
                    //  読取りに失敗した場合
                    return new ParseInfo(false, null, position);
                }
            };
        return func;
    }
    /**
     *  指定したパーサの処理を指定した回数以上繰り返すパーサ(関数)を作成する
     * @param {...parser or integer} parsersAndMin    指定したパーサ,   最小繰返し回数
     * @return {function}               生成した指定したパーサの繰り返すを表現するパーサ
     */
    repeate(...parsersAndMin) {
        const methodName = 'repeate';
        let min = parsersAndMin.pop();
        let parsers = parsersAndMin;
        if (isNaN(min)) {
            parsers.push(min);
            min = 0;
        }
        //  parserをsequnce化
        const parser = this.sequnce(...parsers);
        if (parser === null) { console.error(`${methodName}: parser is null.`); }
        if (parser === undefined) { console.error(`${methodName}: parser is undefined.`); }
        if (!parser instanceof Function) { console.error(`${methodName}: parser is not a function.`); }
        const func =
            /**
             *  生成した指定したパーサの処理を繰り返すパーサ
             * @param {string} target       ソースコード
             * @param {integer} position    読取り位置
             * @return {ParseInfo}          パースした結果
             */
            (target, position) => {/*repeat*/
                if (this.fSpaceSkip) { [target, position] = this.skipSpace(target, position); }
                let count = 0;
                let result = [];                                // スタックを確保
                while (true) {
                    const parsed = parser(target, position);    //  とりあえず実行
                    if (parsed === undefined) {
                        continue;
                    }
                    if (parsed.success) {                       //  成功した場合は
                        result.push(parsed.result);             //      結果をスタックに保持
                        position = parsed.position;             //      読取り位置を更新
                        count++;
                    } else {                                    //  失敗した場合は
                        break;                                  //      ここで中断
                    }
                }
                // end of while
                return new ParseInfo((min <= count) ? true : false, result, position);      //  読取った結果を返す  ※常にTrueを返す
            };
        return func;
    }
    /**
     *  選択パーサを作成する
     * @param {function, Array of function} ...parsers    パーサの配列
     * @return {function}           生成した選択パーサ
     */
    choice(...parsers) {
        const methodName = 'choice';
        // パーサに正規表現やテキストが混ざっていたらパーサに変換する
        parsers = this.normalizeParses(parsers);
        if (parsers === null) { console.error(`${methodName}: parsers is undefined.`); }
        if (parsers === undefined) { console.error(`${methodName}: parsers is undefined.`); }
        // パーサが単体の場合はそのまま返す
        if (parsers.length === 1) return parsers[0];
        // パーサが複数の場合は、いづれかが成功した場合に成功を返すパーサを渡す
        const func =
            /**
             *  生成した選択パーサ
             * @param {string} target       ソースコード
             * @param {integer} position    読取り位置
             * @return {ParseInfo}          パースした結果
             */
            (target, position) => {/*choice*/
                if (this.fSpaceSkip) { [target, position] = this.skipSpace(target, position); }
                for (var i = 0; i < parsers.length; i++) {
                    var parsed = parsers[i](target, position);
                    if (parsed === undefined) {
                        continue;
                    }
                    if (parsed.success) {                           // パース成功したら結果をそのまま帰す
                        return parsed;
                    }
                }
                return new ParseInfo(false, null, position);
            };
        return func;
    }
    /**
     *  パーサに正規表現やテキストが混ざっていたらパーサに変換する
     * @param {parser, Array of parser} parser
     */
    normalizeParses(parser) {
        //  配列の場合
        if (Array.isArray(parser)) {
            const parsers = parser;
            for (let i = 0; i < parsers.length; i++) {
                parsers[i] = this.normalizeParses(parsers[i]);
            }
        } else {
            //  配列以外の場合
            //  regexp
            if (parser instanceof RegExp) {
                parser = this.regexp(parser);
            }
            //  string
            if (typeof parser === "string" || parser instanceof String) {
                parser = this.token(parser);
            }
        }
        return parser;
    }
    /**
     *  連結パーサを作成する
     * @param {function, Array of function} parsers...    結合するパーサの配列
     * @return {function}           生成した連結パーサ
     */
    sequnce(...parsers) {
        const methodName = 'sequnce';
        // パーサに正規表現やテキストが混ざっていたらパーサに変換する
        parsers = this.normalizeParses(parsers);
        if (parsers === null) { console.error(`${methodName}: parsers is undefined.`); }
        if (parsers === undefined) { console.error(`${methodName}: parsers is undefined.`); }
        // パーサが単体の場合はそのまま返す
        if (parsers.length === 1) return parsers[0];
        // パーサが複数の場合は、全て成功した場合に成功を返すパーサを渡す
        const func =
            /**
             *  生成した連結パーサ
             * @param {string} target       ソースコード
             * @param {integer} position    読取り位置
             * @return {ParseInfo}          パースした結果
             */
            (target, position) => {/*sequnce*/
                if (this.fSpaceSkip) { [target, position] = this.skipSpace(target, position); }
                const oldPosition = position;
                let result = [];
                for (let i = 0; i < parsers.length; i++) {
                    const parsed = parsers[i](target, position);
                    if (parsed === undefined) {
                        continue;
                    }
                    if (parsed.success) {
                        result.push(parsed.result);
                        position = parsed.position;
                    } else {
                        parsed.position = oldPosition;
                        return parsed;                              // 一つでも失敗を返せば、このパーサ自体が失敗を返す
                    }
                }
                return new ParseInfo(true, result, position);
            };
        return func;
    }
    /**
     *  オプションパーサを作成する
     * @param {function, Array of function} parser
     * @return {function} 生成したオプションパーサ
     */
    option(...parsers) {
        const methodName = 'option';
        // パーサに正規表現やテキストが混ざっていたらパーサに変換する
        parsers = this.normalizeParses(parsers);
        if (parsers === null) { console.error(`${methodName}: parsers is undefined.`); }
        if (parsers === undefined) { console.error(`${methodName}: parsers is undefined.`); }
        const parser = this.choice(...parsers);
        if (!parser instanceof Function) { console.error(`${methodName}: parser is not a function.`); }
        const func =
            /**
             *  生成した連結パーサ
             * @param {string} target       ソースコード
             * @param {integer} position    読取り位置
             * @return {ParseInfo}          パースした結果
             */
            (target, position) => {
                if (this.fSpaceSkip) { [target, position] = this.skipSpace(target, position); }
                var result = parser(target, position);          //  とりあえず実行
                //  パースに成功したら、その結果を返す
                //  失敗したら、エラった訳では無いので、trueを返す
                return (result.success) ? result : new ParseInfo(true, null, position);
            };
        return func;
    }
    /**
     *  正規表現パーサを作成する
     * @param {RegExp} regexp_      正規表現
     * @return {function}           生成した正規表現パーサ
     */
    regexp(regexp_) {
        const methodName = 'regexp';
        if (regexp_ === null) { console.error(`${methodName}: regexp_ is null.`); }
        if (regexp_ === undefined) { console.error(`${methodName}: regexp_ is undefined.`); }
        if (!regexp_ instanceof RegExp) { console.error(`${methodName}: regexp_ is not a RegExp.`); }
        regexp_ = new RegExp(
            regexp_.source + '|(.*)',
            (regexp_.global ? 'g' : '') +
            (regexp_.ignoreCase ? 'i' : '') +
            (regexp_.multiline ? 'm' : '')
        );
        const func =
            /**
             *  生成した正規表現パーサ
             * @param {string} target       ソースコード
             * @param {integer} position    読取り位置
             * @return {ParseInfo}          パースした結果
             */
            (target, position) => {/*regexp*/
                if (this.fSpaceSkip) { [target, position] = this.skipSpace(target, position); }
                regexp_.lastIndex = 0;
                var regexResult = regexp_.exec(target.slice(position));     //  とりあえず正規表現で実行
                const last = regexResult.pop();
                if (last === undefined) {                                   //  パターン最後の(.*)がundefinedならexecは成功
                    position += regexResult[0].length;                      //      読取り位置を更新し
                    return new ParseInfo(true, regexResult[0], position);   //      正規表現の結果を返す
                } else {
                    return new ParseInfo(false, null, position);            //  nullなので失敗
                }
            };
        return func;
    }
    /**
     *  遅延実行するパーサを作成する
     * @param {function} callback   遅延実行する処理
     * @return {function}           生成した遅延実行するパーサ
     */
    lazy(callback) {
        const methodName = 'lazy';
        if (callback === null) { console.error(`${methodName}: callback is null.`); }
        if (callback === undefined) { console.error(`${methodName}: callback is undefined.`); }
        if (!callback instanceof Function) { console.error(`${methodName}: callback is not a function.`); }
        var parse;
        const func =
            /**
             *  生成した遅延実行するパーサ遅延実行するパーサ
             * @param {string} target       ソースコード
             * @param {integer} position    読取り位置
             * @return {ParseInfo}          パースした結果
             */
            (target, position) => {/*lasy*/
                if (this.fSpaceSkip) { [target, position] = this.skipSpace(target, position); }
                if (!parse) {
                    parse = callback();
                }
                return parse(target, position);
            };
        return func;
    }
    /**
     *  パーサの結果を加工するパーサを作成する
     * @param {function} parsers    sequnce用パーサ
     * @param {function}    fn      結果を加工する処理
     * @return {function}           生成した遅延実行するパーサ遅延実行するパーサ
     */
    map(...parsersAndFn) {
        const methodName = 'map';
        const fn = parsersAndFn.pop();    // 最後は結果を加工する処理
        const parsers = parsersAndFn;
        // fn定義漏れチェック
        if (fn === undefined) { console.error(`${this.className}.${methodName}: fn is undefined.`); return undefined; }
        if (!fn instanceof Function) { console.error(`${this.className}.${methodName}: fn not is Function.`); return undefined; }
        //  parserをsequnce化
        const parser = this.sequnce(...parsers);
        if (parser === null) { console.error(`${methodName}: parser is null.`); }
        if (parser === undefined) { console.error(`${methodName}: parser is undefined.`); }
        if (!parser instanceof Function) { console.error(`${methodName}: parser is not a function.`); }
        const func =
            /**
             *  生成した遅延実行するパーサ遅延実行するパーサ
             * @param {string} target       ソースコード
             * @param {integer} position    読取り位置
             * @return {ParseInfo}          パースした結果
             */
            (target, position) => {/*map*/
                if (this.fSpaceSkip) { [target, position] = this.skipSpace(target, position); }
                const result = parser(target, position);
                if (result.success) {
                    const resultFn = fn(result.result);
                    return new ParseInfo(result.success, resultFn, result.position);
                } else {
                    return result;
                }
            };
        return func;
    }
}

今回の「車輪の再発明」

文法解析は再呼び出しダラケなので、正直コールスタックを読んでも訳が判らない。

const _debugPrint = (target, position) => {
    const text = this.positionText(target, position);
    console.log(`_trap: '${text}'`);
    return undefined;
};

これをパーサのパラメータの文法部分に

const syntax = _map(
    rule_list, _debugPrint, /\s*/
    , (parsed) => {
        return { _syntax: parsed[0] };
     });

のように差し込むと通過した時の文字の位置がコンソールに出るのでソコからあたりを付けられる。

※実績:

一度もword部まで到達してないっぽい?

どうやらコメントのトコの正規表現が怪しいなぁ?

そんな時には使えるデバッグプリント。




コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA