変奏現実

パソコンやMMORPGのことなどを思いつくまま・・・記載されている会社名・製品名・システム名などは、各社の商標、または登録商標です。

この画面は、簡易表示です

インターネット

[javascript]spreadSheet4 コードのオブジェクト化

テキストを構文解析したパースした結果のオブジェクトはindexedDBに保存してあるけど、オブジェクトをテキストに戻したり、計算させるコードはJavaScriptのままだったので、中途半端だった。

あまり複雑なコトは出来なくていいので、オブジェクトをstringifyしたりcalulateしたりexecuteできるオブジェクトを考えてparserオブジェクトストアに保存すればいいかな?

そう考えると、まず俺々EBNFパーサで試してみるのが良さそうだ。

  • パースするEBNFなテキストを用意する
  • parserオブジェクトストアから俺々EBNFパーサを読みだす
  • パース部分とパース結果を手直す部分に分離
  • パース部分からdefinitionっぽいパーサ・コンビネーションを作る
  • パース結果を手直す部分から実行コードを作る
  • parseメソッドに先のテキストを読ませる
  • 何かの構文解析パーサができる

な感じだろうか

とりあえずはmakeParserメソッドを「パースした結果のオブジェクト」っぽいオブジェクトに展開。

するところから始め、stringify、calculateも「パースした結果のオブジェクト」っぽいオブジェクトに展開してみよう。

多分、

{ function_definiton: { name: x..., procedure:[.....] } }
{ return: {expr:... } }
{ if:{expr:... }, then:{...}, else:{...} }
{ while:{expr }, procedure:[.....]},
  {continue:null},
  {break:null},
{ function:{name:x..., parameter:{... } } }
{ expr:[x..., '+', x..., ...] }
{ value:{ type;integer, value:1 } }
{ set:{ name:x... }, value:{ expr or value:.... } }
{ try:[...], catch:[...], finally:[...] }

とかになるんだろう。+-と/*演算子の優先度は{}のネストでカバーすればいいし。

{ export: x..., from y... }
{ import: x..., from y... }

も必要か、javascriptのコードの呼び出しは、事前に「{function_definiton」の内部に登録しておこう。

execObjectList.map((execObject) => {
  switch() {
    case 'function_definition':
        {
        }
    ....
  }
});

と、実行用オブジェクトを種類別に仕分けてコード化すれば何とかなるだろう。



「javascript」spreadSheet4 EBNFの「,」

やっとマクロ構文を組み込み始めたが・・・

デリミタではまった。

(*$ imports: expression="EXPR" $*)
(*$ SEQUENCE SEPARATOR IS REQUIRED $*)
(* マクロ *)
macro               = { ( class declaration | function declaration | variable declaration ) }, /\s*/ ;
class declaration   = "class", class name, "{", { member declaration | method declaration } "}" ;
class name          = identifier ;
comment             = "/*", /.*/, "*/" | "//", /.*/, "\n" ;
member declaration  = [ "const" | "static" ], member name, [ "=", expression], { ",", member name, [ "=", expression] } ";" ;
member name         = identifier ;
method declaration  = [ ? ebnf reference jdoc.document ? ], [ method name ], parameter list, procedure list ;
method name         = identifier ;
function declaration= [ ? ebnf reference jdoc.document ? ], [ function name ], parameter list, procedure list ;
function name       = identifier ;
parameter list      = "(", [ parameter name, [ "=", expression ], { ",", parameter name, [ "=", expression ] } ], ")" ;
parameter name      = identifier ;
procedure list      = "{", procedure, "}";
procedure           =    {
                         (
                         | variable declaration 
                         | assignment expression
                         | branch procedure
                         | iterative procedure
                         | exception procedure
                         )
                      } ;
variable declaration= [ "const" | "let" | "static" ], variable name, [ "=", expression], { ",", variable name, [ "=", expression] } ";" ;
variable name       = identifier ;
assignment expression= variable name, [ ".", member name ] "=" expression;
branch procedure    = if procedure | switch procedure ;
if procedure        = "if", "(", expression, ")", procedure list, { "else", if procedure } ;
switch procedure    = "switch", "{", { case procedure } "}" ;
case procedure      = ( "case", expression | "default" ), ":", { procedure }, [ "break" ] ;
iterative procedure = for procedure | while procedure | do while procedure ;
for procedure       = "for", "(", assignment expression, ",",  expression  ",",  assignment expression ")" ;
while procedure     = "while", "(", expression ")", procedure list ;
do while procedure  = "do", "(", expression ")", procedure list, "while", "(", expression ")" ;
exception procedure = "try", procedure list, "catch", "(", parameter name ")", procedure list, [ "finally", procedure list ]
                    |  "throw", expression ;
identifier          = { comment }, /[A-Za-z][A-Z_a-z]/, { comment } ;

どこが間違っているのかな?

(*$ import expr from "EXPR" $*)
(*$ SEQUENCE SEPARATOR IS REQUIRED $*)
(* マクロ *)
macro               = { class declaration | function declaration | variable declaration }, /\s*/ ;
class declaration   = "class", class name, "{", member declaration, { method declaration }, "}" ;
class name          = identifier ;
comment             = "/*", /.*/, "*/" | "//", /.*/, "\n" ;
member declaration  = [ "const" | "static" ], member name, [ "=", expression], { ",", member name, [ "=", expression] }, ";" ;
member name         = identifier ;
method declaration  = [ ? ebnf reference jdoc.document ? ], [ method name ], parameter list, procedure list ;
method name         = identifier ;
function declaration= [ ? ebnf reference jdoc.document ? ], [ function name ], parameter list, procedure list ;
function name       = identifier ;
parameter list      = "(", [ parameter name, [ "=", expression ], { ",", parameter name, [ "=", expression ] } ], ")" ;
parameter name      = identifier ;
procedure list      = "{", procedure, "}";
procedure           =    { variable declaration 
                         | assignment expression
                         | branch procedure
                         | iterative procedure
                         | exception procedure
                         } ;
variable declaration= [ "const" | "let" | "static" ], variable name, [ "=", expression], { ",", variable name, [ "=", expression] }, ";" ;
variable name       = identifier ;
assignment expression= variable name, [ ".", member name ],"=", expression;
branch procedure    = if procedure | switch procedure ;
if procedure        = "if", "(", expression, ")", procedure list, { "else", if procedure } ;
switch procedure    = "switch", "{", { case procedure }, "}" ;
case procedure      = ( "case", expression | "default" ), ":", { procedure }, [ "break" ] ;
iterative procedure = for procedure | while procedure | do while procedure ;
for procedure       = "for", "(", assignment expression, ",", expression, ",", assignment expression, ")" ;
while procedure     = "while", "(", expression, ")", procedure list ;
do while procedure  = "do", "(", expression, ")", procedure list, "while", "(", expression, ")" ;
expression          = expr ;
exception procedure = "try", procedure list, "catch", "(", parameter name, ")", procedure list, [ "finally", procedure list ]
                    |  "throw", expression ;
identifier          = { comment }, /[A-Za-z][A-Z_a-z]/, { comment } ;

違いが判るだろうか?

最初は [ … […] … ] なんて囲み文字のネストは考えて無かったw!とか慌てたけど、そんなことを気にする様な気難しい文法解析方法は今回実装していない。

正解は・・・

  • importが旧仕様
    • ☓ imports: expression=“EXPR”
    • 〇 import expr from “EXPR”
  • sequenceのデリミタ(カンマ)がところどころ抜けている
    • 「}」の後
      • { … } , “;”
        • 「}」の後にトークンがあるなら、カンマで区切る必要がある
    • 「”}”」や「”)”」の前
      • { … } の } の前にカンマは不要、 ( … ) なら ) の前にカンマは不要
      • “{” … “}” の “}” の前にカンマは必須、 “(” … “)” なら “)” の前にカンマは必須

いづれも独自の仕様と云えば独自なのかもしれないけどね。

ps.import の処理が間違っていたので修正。EbnfParserクラスのparseメソッドを実行すると、export、importの情報が履歴の様に残っていたので、parseメソッド内でクリアするように変えるのでシャローコピーを各パーサに引き渡す様にした。

ps.2024/4/25

マクロファイルのアップロード、パーサ、indexedDB処理までできた。後はパーサのMap処理と実行部。どうすればいいのかは・・・やってみないと判らない。(笑



[javascript]spreadSheet4 クラス継承の見直し

  • CoreParsser
    • Parser
      • EbnfParser
        • CellValueParser
        • CellExprParser
        • ExportParser
        • ImportParser

とクラス継承をしているのに同じ名前のメソッドが内容は互いに独立しているため、ローカルメソッド名を使ってしのいでいたが、ほぼローカルメソッド名になってしまったので・・・・

CellValueParser以下は継承せず、フィールドにEbnfParserを保持し、

this._ebnfParser.parse('xxxxxxxxxxxxx')

のように変更した。

  • CellValueParser
  • CellExprParser
  • ExportParser
  • ImportParser
  • CoreParsser
    • Parser
      • EbnfParser

これにより、微妙だった初期化の手順が

  • SpreadSheetクラスで、EbnfParserクラスオブジェクトを生成しEbnfパーサを保持
    • EbnfParserクラスで、ExportParserとImportParserクラスオブジェクトを生成しExportとImportパーサを保持
  • SpreadSheetクラスで、CellValueParserクラスオブジェクトを生成しCellの値パーサを保持
  • SpreadSheetクラスで、CellExprParserクラスオブジェクトを生成しCellの数式パーサを保持

と安定してきたし、EBNFテキストでパーサを生成するクラスが全て独立したので、セルフテストの重複が減った。しかしsuper()でパーサを作るのが難しくなり、makeParserとselfTestに関連する処理をsetup()に纏める必要が出てきたついでにテンプレを作り、

/**
 *  EBNF関連のパーサのテンプレ
 */
class EbnfParserTemplate {
    /**
     *  パーサクラスオブジェクト
     * @memberof EbnfParserTemplate
     */
    _parser = null;
    /**
     *  自前のパーサ
     * @memberof EbnfParserTemplate
     */
    myParser = null;
    /**
     *  クラス名
     * @memberof EbnfParser
     */
    #className = 'EbnfParserTemplate';
    className = this.constructor.name;
    /**
     *  コンストラクタ
     */
    constructor() {
        //  パーサクラスオブジェクトを生成する
        this._parser = new Parser();
    }
    /**
     *  パーサ初期設定
     * @param {SelfTestInfo} fSelfTest 
     */
    setup(fSelfTest = false) {
        //  自前のパーサを生成する
        console.log(`${this.className}.constructor: makeParser().`);
        this.myParser = this.makeParser();
        //  セルフテストする
        if (fSelfTest) {
            const bk = this.DEBUG;
            this.DEBUG = fSelfTest;
            const selfTestRessult = this.selfTestList(this.getSelfTestList(), this.myParser);
            this.DEBUG = bk;
        }
    }
    /**
     *  パーサ生成 
     * @returns 
     */
    makeParser() {
        const myParser = undefined;
        return myParser;
    }
    /**
     *  EBNFルールと文法を作成
     * @param {string} ebnf             BNFテキスト
     * @param {Function} syntax         パーサ
     * @param {Function} evalProcInfo   パースした結果の評価関数情報
     * @returns {EbnfParseMethodResult} パース結果
     */
    parse(ebnfText, parser = this.myParser, evalProcInfo) {
        //  パース結果
        const parseResult = new EbnfParseMethodResult(/*success, result, position, result.result, definitionList, evalProcInfo*/);
        return parseResult;
    }
    /**
     * テスト処理
     * @param {Array of strung} testPatternList 
     * @param {function} parser
     * @returns {Array of ParseResult}
     */
    selfTestList(testPatternList, parser) {
        const methodName = 'selfTestList';
        const info = this.DEBUG;
        const parseResultList = testPatternList.map((test, index, a) => {
            if (info) {
                console.log(`${this.className}.${methodName}: pattern[${index + 1}/${a.length}]`);
            }
            return this.selfTest(test, parser);
        });
        return parseResultList;
    }
    /**
     * テスト処理
     * @param {string} testPattern 
     * @param {function} parser
     * @returns {Array of ParseResult}
     */
    selfTest(testPattern, parser) {
        const parseResult = this.parse(testPattern, parser);
        return parseResult;
    }
    /**
     * テストデータ取得
     * @returns {array of object}   テストデータリスト
     */
    getSelfTestList() {
        return [];
    }
}

全般的にExport、Import、Ebnf、セル値、セル数式のパーサクラスを見直したら、ゴチャゴチャしていたコンストラクタがすっきりした。

class  xxxParser extends EbnfParserTemplate{
    ・・・
    /**
     *  コンストラクタ
     * @param {EbnfParser} ebnfParser
     */
    constructor(ebnfParser) {
        super();
        this._ebnfParser = ebnfParser;
        this.setup(new SelfTestInfo(true, false, false, false));
    }
    ・・・
}

今現在のクラス継承はこんな感じ

  • CoreParser
    • Parser
  • EbnfParserTemplate
    • _parser <- new Parser()
    • EbnfParser
      • _parserList <- new ExportParser()、new ImportParser()
    • ExportParser
      • _ebnfParser <- コンストラクタのebnfParserパラメータ
    • ImportParser
      • _ebnfParser <- コンストラクタのebnfParserパラメータ
    • CellValueParser
      • _ebnfParser <- コンストラクタのebnfParserパラメータ
    • CellExprParser
      • _ebnfParser <- コンストラクタのebnfParserパラメータ



[javascript]spreadSheet4 repeateパーサの見直し

何故かEBNFのrepeateパーサだけ ‘{‘, sequence, ‘}’になっていて、気になっていた。

//  repeate     =   "{" sequence "}" ;
const repeate = _lazy(() => {
    return _map('repeate',
        '{', sequence, '}'
        , (parsed) => {
            parsed = this.arrayFlat([], parsed);
            const lparen = parsed[0];
            const seq = parsed[1];
            const rparen = parsed[2];
            let rc = seq['#sequence'];
            return { '#repeate0': rc };
        });
});

他に合わせて'{‘, choice, ‘}’にするとエラってしまう。

仕方が無いから、パースの動きをずーっと追っていくと・・・・・・・・・・・・・・・・・・・・・

灯台元暗しで、このパーサで、return { ‘#repeate0’: undefined }になっていた。

原因は、seq[‘#sequence‘];

choiceは、{#choice: xxxx}と返すので、当然の結果。

うっかりミスを減らす様に修正。

//  repeate     =   "{" choice "}" ;
const repeate = _lazy(() => {
    return _map('repeate',
        '{', choice, '}'
        , (parsed) => {
            parsed = this.arrayFlat([], parsed);
            const choiceResult = parsed[1];
            //  同じ繰り返しなら省略
            if (choiceResult['#repeate0']) {
                return choiceResult;
            }
            return { '#repeate0': choiceResult };
        });
});

やっとこれで、

{ ( A | B | C) }

の様に助長的な書き方をしなくても、

{ A | B | C }

で済むようになったし、

choice = sequence, { '|', sequence } ;

なので、{ … } の中は choiceでもsequenceでもOK。

しかし、

  • 数式のrepeate ※まだ未実装
  • ENBFのrepeate ※今回の主役
  • パーサ・クラスのrepeate ※今回の悪役
  • コア・クラスのrepeate ※今回はモブ

と、repeateが多くて紛らわしい。

ENBFのrepeateの文法は、'{‘, choice, ‘}’ になって便利になった。

しかし、パーサ・クラスのrepeateのパラメータは、ebnfParserのmakeParserメソッド中でmapメソッドのパラメータの文法部分をsequenceっぽく書きたいので、sequenceにまとめて処理している。目的が違うから仕様も違うんだけど、後で見返すとちんぷんかんぷんになってしまう。

動作確認をしているとパースの結果の内部構造が倍くらいに長くなっていたので短くした。

{
"syntax": {
  "#syntax": {
    "#rule_list": [
      {
        "rule_list": {
          "#definition": {
            "#repeate1": {
              "#choice": [
                { "#identifier": "rule" },
                { "#identifier": "special_effect" },
                { "#identifier": "comment" }
]}}}}]}}


[javascript]spreadsheet4 EBNF imports,exports

一度にマクロまで拡張すると修正が多そうなので、一旦、数式と値にEBNFを分割し、数式のEBNFから値のEBNFの(非)終端記号を参照できる様な仕組み(imports,exports)を作ってみた。

(*$ SEQUENCE SEPARATOR IS NOT REQUIRED $*)
(*$ export {value_cell,time_stamp,date,number,boolean,sqstring as singleQuoteString,text} from "CELL_VALUE"  $*)
(* セルのデータ *)
value_cell          = time_stamp | date | time | number | boolean | sqstring | text ;
time_stamp          = date ' ' time ;
date                = yyyy '/' MM '/' dd  | MM '/' dd ;
yyyy                = /\\d{4}/ ;
MM                  = /\\d{1,2}/ ;
dd                  = /\\d{1,2}/ ;
time                = HH ':' mm ':' ss | HH ':' mm  ;
HH                  = /\\d{1,2}/ ;
mm                  = /\\d{1,2}/ ;
ss                  = /\\d{1,2}/ ;
number              = /[-]?([0-9]+)([.][0-9]*)?/ ;
boolean             = /true/i | /false/i ;
sqstring            = /'.*/ ;
text                = /.+/ ;
(*$ SEQUENCE SEPARATOR IS NOT REQUIRED $*)
(*$ export expr from "EXPR" $*)
(*$ import defaultExport from "CELL_VALUE" $*)
(* セルのデータ *)
cell                = expr_cell | value_cell ;
(* 数式 *)
expr_cell           = '=' expr ;
expr                = logical_expr ;
logical_expr        = add_sub_expr [ ( '=' | '>' | '<' | '>=' | '<=' | '<>' ) add_sub_expr ] ;
add_sub_expr        = mul_div_expr { ( '+' | '-' | '&' ) mul_div_expr } ;
mul_div_expr        = factor { ( '*' | '/' ) factor } ;
factor              = value | parenthesis_expr ;
parenthesis_expr    = '(' expr ')' ;
value               = number | boolean | function_def | a1_range | a1 | dqstring ;
a1_range            = a1 ':' a1 ;
a1                  = /([A-Za-z]+[1-9][0-9]*)/ ;
dqstring            = /"[^"]*"/ ;
function_def        = symbol '(' parameters ')' ;
symbol              = /([A-Za-z][A-Za-z0-9_]*)/ ;
parameters          = [ expr ] { ',' expr } ;

node.jsのimportを参考にこんな感じになっている。

名前付き import: (*$ import { export1, export2 } from "module-name" $*)
デフォルトの import: (*$ import defaultExport from "module-name" $*)

こうすることで、

(*$ export {value_cell,time_stamp,date,number,boolean,sqstring as singleQuoteString,text} from "CELL_VALUE" $*)

値EBNF側で、{非}終端記号をindexedDBに書き込むtype名でexportする様にすれば、

(*$ import defaultExport from "CELL_VALUE" $*)

importする側はdefaultExportだけで済む。

テキストをガリガリしないでEBNFなimportParserとexportParserクラスを追加し、なんとか最低限の文法(上の2つダケ)を実装した。

(*$ SEQUENCE SEPARATOR IS NOT REQUIRED $*)
(* EXPORT *)
exportSyntax        = "export" members "from" ebnf_export_name;
members             = member | "{" member { "," member } "}" ;
member              = symbol { "as" alias } ;
alias               = symbol;
ebnf_export_name    = dqstring;
dqstring            = /"[^"]*"/ ;
symbol              = /([A-Za-z][A-Za-z0-9_]*)/ ;
(*$ SEQUENCE SEPARATOR IS NOT REQUIRED $*)
(* IMPORT *)
importSyntax        = "import" ( defaultExport | members ) "from" ebnf_export_name ;
defaultExport       = "defaultExport" ;
members             = member | "{" member { "," member } "}" ;
member              = symbol { "as" alias } ;
alias               = symbol;
ebnf_export_name    = dqstring;
dqstring            = /"[^"]*"/ ;
symbol              = /([A-Za-z][A-Za-z0-9_]*)/ ;

これらのEBNFをパースする時点ではまだimportもexportも未登録状態なので、各自がexport処理し、ebnfParserのparseの中で直接importとexportをimportさせた。

当初はebnfParser.#parse内でEBNFテキスト中のexportを処理させたが、cell_valueやcell_exprのパーサのマップを反映する前にimportしてしまったので計算するデータが全て文字になっていたから、各クラスで#makeParser後にebnfParser.exportメソッドを呼びださせた。

後、できるだけ、return [……] や {……} を、return new xxxx(….) に書換え。

なんでもEBNF化すれば良い訳ではないけれど、これに限ってはトコトンEBNF化しないと・・・



[javascript]spreadsheet4 special-effect (*$ … $*)

コメント(* … *)を独自に拡張し、(*$ … $*)の場合には特殊効果(Special Effect)を与えている。

意外と便利。

動作は、EBNFな文法テキストをEBNFパーサで解析する際には、

(*$ SEQUENCE SEPARATOR IS REQUIRED $*)           sequenceのパラメータは必ず[、]を使用する
(*$ SEQUENCE SEPARATOR IS NOT REQUIRED $*)       sequenceのパラメータは[  ]か[、]を使用する
(*$ DEBUG PRINT $*)  デバッグメッセージ用     EBNFテキストに差込みメッセージを表示させる

EBNFな文法テキストから作成したパーサで数式等を解析する際には、

(*$ NO SPACE SKIP $*) 字句判定前に空白をスキップしない
(*$ SPACE SKIP $*)  字句判定前に空白をスキップする
(*$ DEBUG PRINT $*)  デバッグメッセージ用 mapメソッドのsequenceパラメータ部に差込みメッセージを表示させる

とするつもりだったが、EBNFな文法テキストを読んでいる時に動作していたので、修正した。

Special-Text( ? … ? )として、他のEBNFテキストを参照する場合に ( ? jdoc ? :jdocを参照する)と云う感じにする予定。



[javascript]spreadsheet4 デバッグ

再帰しまくりなパーサ・コンピネータのデバッグは、ぐるぐると動き回るパーサがEBNFのどの辺を処理しようとしているのかはテキスト(target)と読取り位置(position)で把握できるけど、スタックコールにはsequenceやmapぐらいしか見えないから、非終端記号(EBNFの左辺の識別子(名前))レベルでパーサのネストの進行状態をイメージするのは難しい。

強固な防壁魔法の呪文を観て分析し解除するのに似ている。

平たく言えば、スタックコールのsequenceやmapの組み合わせを観て、虫を刈り取るイメージができるかどうかにかかっている。

大体、groupやoptionの配下にもchoiceやsequenceがダラダラと入り込むのでイメージするのは容易ではないから、サクサクと進む訳も無く、瞑想に頼るしかない。

そろそろ、そんな状況もかったるいので、

非終端記号はParser.map()(パラメータのsequence処理が成功したらコールバックを呼ぶパーサ)をよく使うのでmapのパラメータに非終端記号名を追加し、解析時にスタックし、mapのパース処理後に処理結果と非終端記号のネスト具合をログに書き出す様にセル手入力直後のパースにログを入れてみた。

async closeEntry(fSave = true) {
・・・
_exprParser.map.DEBUG = true;
const parseResult = _exprParser.parse(text);
_exprParser.map.DEBUG = false;
・・・
}
["expr_cell","logical_expr","add_sub_expr","mul_div_expr","number"]: x
["expr_cell","logical_expr","add_sub_expr","mul_div_expr","boolean"]: x
["expr_cell","logical_expr","add_sub_expr","mul_div_expr","function_def","symbol"]: ["B","1"]
["expr_cell","logical_expr","add_sub_expr","mul_div_expr","function_def"]: x
["expr_cell","logical_expr","add_sub_expr","mul_div_expr","a1_range","a1"]: ["B","1"]
["expr_cell","logical_expr","add_sub_expr","mul_div_expr","a1_range"]: x
["expr_cell","logical_expr","add_sub_expr","mul_div_expr","a1"]: ["B","1"]
["expr_cell","logical_expr","add_sub_expr","mul_div_expr"]: [{"type":"a1","address":[{"row":1,"col":2}]},[]]
["expr_cell","logical_expr","add_sub_expr"]: [{"type":"a1","address":[{"row":1,"col":2}]},[]]
["expr_cell","logical_expr"]: [{"type":"a1","address":[{"row":1,"col":2}]},null]
["expr_cell"]: ["=",{"type":"a1","address":[{"row":1,"col":2}]}]

順に見ていくと、

  • number、booleanと失敗し
  • function_def.symbolで B1 を判定したものの、
    • 多分、「(…)」が無いので、function_defを諦め、
  • a1_range.a1でB1を再度判定したものの、
    • 多分、「:」が無いのでa1_range諦め、
  • a1で渋々納得し、
  • パーサのネストを駆け戻り、
  • mul_div_expr、add_sub_expr、logical_expr、expr_cellを経て、
  • [“=”,{“type”:”a1″,”address”:[{“row”:1,”col”:2}]}]を得ている。

と大雑把に動きが見えるようになった。

しかし、多分、「xx」が無い、部分はまだ推測するしかないのでtokenやregexpパーサにも解析ログを付けたら、エラーチェック実装時に「「xx」が来ると予想されたxxx文字目に「yy」がありました。」とか出せそうな気がしたけど、

["expr_cell","o: '=","logical_expr","add_sub_expr","mul_div_expr","number","x: '[-]?([0-9]+)([.][0-9]*)?'"]: x
["expr_cell","o: '=","logical_expr","add_sub_expr","mul_div_expr","boolean","x: 'true'","x: 'false'"]: x
["expr_cell","o: '=","logical_expr","add_sub_expr","mul_div_expr","function_def","symbol","o: 'B1"]: ["B","1"]
["expr_cell","o: '=","logical_expr","add_sub_expr","mul_div_expr","function_def","x: '('"]: x
["expr_cell","o: '=","logical_expr","add_sub_expr","mul_div_expr","a1_range","a1","o: 'B1"]: ["B","1"]
["expr_cell","o: '=","logical_expr","add_sub_expr","mul_div_expr","a1_range","x: ':'"]: x
["expr_cell","o: '=","logical_expr","add_sub_expr","mul_div_expr","a1","o: 'B1"]: ["B","1"]
["expr_cell","o: '=","logical_expr","add_sub_expr","mul_div_expr","x: '*', '/'"]: [{"type":"a1","address":[{"row":1,"col":2}]},[]]
["expr_cell","o: '=","logical_expr","add_sub_expr","x: '+', '-', '&'"]: [{"type":"a1","address":[{"row":1,"col":2}]},[]]
["expr_cell","o: '=","logical_expr","x: '=', '>', '<', '>=', '<=', '<>'"]: [{"type":"a1","address":[{"row":1,"col":2}]},null]
["expr_cell","o: '="]: ["=",{"type":"a1","address":[{"row":1,"col":2}]}]

「x:’(’」 は良い感じだけど、非終端識別子の終わりにx:がいっぱい付いているのは・・・

あった方が良い様な、最後のxだけで良い様な、微妙な感じ。

先は長いなぁ~



[javascript]spreadsheet4

EBNFベースで数式の文法を書いたバージョンです。

やってみた感じでは・・・ただただ内部が面倒くさくなったダケでした。

なぜか、「1+2+3」が式として認識されるので、まだ大きなミスが残ってそう。

  • EBNF文法を解析するパーサ
    • セルフテスト
      • EBNF文法で書いたEBNFの文法のテキストを文法解析させJSONデータを得る。
        • 全文解析できればOK
        • 使用する正規表現自身で字句解析は無理っぽいので簡易なパターンが通ればOK
    • EBNF文法で書いた数式の文法のテキストを文法解析させJSONデータを得て、一部識別子の宣言にmapで解析結果をトークン化しやすくしておく。
    • 数式のテキストを文法解析させ数式のトークンリストを得る。
  • トークン関係
    • 数式のトークンリストはindexedDBのexprプロパティに格納する。
    • 数式のトークンリストで計算し結果をindexedDBのvalueプロパティに格納する。
  • 画面関係
    • 数式のトークンリストをテキスト化したものを画面のテーブルのセルにexpr属性として貼り付ける。
    • セル上にマウスポインタを置くと、ツールチップでセルの内容を確認できる。

特にセルフテストで正規表現をEBNF文法で、

regexp ::=  "/"  /.+/ "/" /[dgimsuvy]*/

と、こんな雰囲気で書くと間に隙間(空白)が入ってもOKになってしまうし、だからと云って正規表現でまとめると読みにくい上に、new RegExpが「オプションの指定が矛盾しています」と云ってくるので悩ましい。

今のところ、特定の識別子の定義で空白をスキップしたく無い場合は、no_space_skipを混ぜている。

const no_space_skip = function () {
    this.fSpaceSkip = false;
    return undefined;
}.bind(this);
・・・
//  wq_string   =   '"' /(?!\\\\)[^"]*/ '"' ;
const wq_string = _map(
    no_space_skip, '"', /(?!\\)[^"]*/, '"'
    , (parsed) => {
        parsed = parsed[1]
        return { _wq_string: `${parsed}` };
    });
・・・
sequence(...parsers) {
・・・
const func =
    (target, position) => {/*sequnce*/
        const bk = this.fSpaceSkip;  ☚空白読飛状態をバックアップ
・・・
        for(let i=0;i<parsers.length;i++) {
            parsers[i](target, position);  ☚この中でno_space_skipが空白読飛状態を変更
        }
・・・
        this.fSpaceSkip = bk;     ☚空白読飛状態を復元
        return 
    }
・・・
    return func;
}

no_space_skipを内包するsequenceとその配下での空白読飛しを停止できるが、

今のところsequenceパーサのパラメータにno_space_skipを直挿しするしか方法が無い。

EBNFテキストのsequence定義文のデリミタ「、」に「+」を加え、「+」ならconcat風に見えるので「空白読み飛ばし停止」的な感じにしたい。あと、先頭にも[ + ]が使えた方がいいかもしれない。

ps.2024/4/5

Ctrl+SでJSON形式でテーブルのデータをそのままダウンロード。そのファイルをテーブルにドロップするとアップロード。但し、indexedDBの検索が不調で連動計算がうまくいかない。setTimeoutで適当に間を開けてもダメなので、トランザクションがちゃんと終わるのを待たないとダメかな?と思ったら、参照セルアドレスの保存方法を修正漏れだった。setTimeoutも不要だった。

また数式の参照先が空っぽで動きを追っていくと、パーサのコールバック内でメソッド内の変数を更新しても、アロー関数作成時点のメソッドの変数の残像(ディープコピー)の方を更新しているみたいで当のメソッド内変数が更新されない。当初のパーサコンビネータ方式ではメソッドを呼び出す度にコールバックを作り直していた(のでちょっと遅い)支障なかったが、今回は式クラスのコンストラクタで1度だけ生成するようにしたせいで相違が出た。

/**
 * スプレッドシートの数式クラス
 */
class SpreadSheetExprParser {
    /**
     *  コンストラクタ
     * @param {SpreadSheetCustomElement} spreadSheet 
     */
    constructor(spreadSheet) {
        const _exprParser = this;
        _exprParser.className = _exprParser.name ?? _exprParser.constructor.name;
        _exprParser._spreadSheet = spreadSheet;

        this.parseEBNF(undefined);  // self test
        this.parse('=1+2+3');       // expression setup ☚ここで仮データで数式のパーサを初期設定。
    }
/**
 *  数式を構文解析する
 * @param {string} text   数式
 */
parse(text) {
    const ebnf = `  (* セルのデータ *)
    cell                ::= expr_cell | value_cell ;
    ・・・数式のEBNFテキスト・・・
    `;
    if (this.exptEbnfAnalize === undefined) {
        this.exptEbnfAnalize = this.parseEBNF(ebnf); ☚ ここで、数式用EBNFテキストを読ませて解析させる。
        ・・・・以下、解析結果をDBに保存できるようにトークン化するコールバック一式を作成。
     ※コンストラクタから呼び出された時にローカルなアロー関数は参照する変数をディープコピーしてるっぽい。
    }
    ・・・・
    //  数式の纏めパーサ
    const cell = this.exptEbnfAnalize.parser['cell']; ☚ ここで数式の文法のトップを調べ、
    var result = cell(target, 0); ☚ ここで数式を読ませ解析させる。この時点で新たなローカル変数が作られ、
                                      上記のローカルなアロー関数がアクセスするローカル変数とは別腹。
    if (!result.success) {
    ・・・・

仕方が無いので解析結果からA1やA1範囲のトークンを探すことにした。

さて、EBNFで今更気が付いたけど要は”…”と/…/opt で表現できる文字列のみでテキストが出来ているので、一気に字句解析させてみようと思ったけど、

sequce( “{” , /.*/ , “}” ) を見ただけで、意味が無いことが判った。

どちらかと云えば、「空白文字の列」と「空白以外の文字の列」に分けて下ごしらえした方がマシ。

パーサのソースが巨大になってきたので、

  • パーサ
  • EBNF構文のパーサ
  • 数式構文のパーサ

に分離した。

wikiのEBNFの内容が充実してきたので、EBNF部分を見直してみると、グループやオプションでも「*」が使える様な雰囲気なので、EBNFのテキストを変えてみたら、rule-listすら通らない!

ENFテキスト中の “{” … “}”と違い、{ … } はEBNFのrepeateパーサを直接呼び出しているのにやっと気づき、EBNFのrepeate、group、optionパーサの文法も修正したので暫くは持ちそう。

(*$ SEQUENCE SEPARATOR IS REQUIRED $*)
(* 文法   *)
syntax      =   rule list, /\s*/ ;
rule list   =   { ( rule | special effect | comment ) } + ;
rule        =   identifier, defining symbol, definitions, ";" ;
defining symbol = "=" | "::=" ;                                     (* 終端記号 '=':EBNF, '::=':BNF *)
(* 定義   *)
definitions =   definition, { "|", definition } ;                   (* 定義リスト *)
definition  =   choice ;                                            (* 定義 *)
(* リスト *)
choice      =   sequence, { "|", sequence} ;                        (* アレかコレかソレかのどれか *)
sequence    =   exception, { ",", exception } ;                     (* アレとコレとソレの一式。","は空白改行と同義  *)
exception   =   words, { "-", words } ;                             (* アレが良いが、コレとソレを除く。 *)
(* 囲み   *)
words      =   [ comments ], word, [ comments ] ;
special effect =  (*$ NO SPACE SKIP $*) "(*$", /[^\\?]*/, "$*)" ;   (* 特殊効果 *)
comments    =   ( special effect | comment ), repeat symbol;
word        =   ( sq string | wq string | wraper | special text | regexp | identifier ) ;
comment     =   (*$ NO SPACE SKIP $*) "(*", /(?![*][/])(.*)/, "*)" ; (* 注意書き *)
sq string   =   (*$ NO SPACE SKIP $*) "'", /(?!\\\\)[^']*/, "'" ;   (* ' で括られたテキスト *)
wq string   =   (*$ NO SPACE SKIP $*) '"', /(?!\\\\)[^"]*/, '"' ;   (* " で括られたテキスト *)
wraper      =   ( group | option | repeate ), repeat symbol ;
group       =   "(", choice, ")" ;                                  (* choiceを明示的に括る表現 *)
option      =   "[", choice, "]" ;                                  (* あれば尚良し *)
repeate     =   "{", sequence, "}" ;                                (* 0回以上繰り返す *: 0回以上、+:1回以上 *)
regexp      =   /.+/i ;                                             (* 正規表現されたテキスト "/"  + /.+/ "/" + { /[dgimsuvy]/ } *)
identifier  =   /[A-Za-z][A-Z_a-z0-9\\x20]*/;                       (* 識別子 *)
special text =  (*$ NO SPACE SKIP $*) "?", /[^\\?]*/, "?" ;         (* 特殊文字列 *)
repeat symbol=  [ "*" | "+" | '{', [ number ], ',', [ number ], '}']; (* 繰返し記号 *)
number      =   /\d+/ ;

EBNFは定義記号が=なので=でも::=でも通る様に修正、未終端記号(識別子のことか?)に空白を挟めるみたいだが、そうなると、sequenceのセパレータを必須にしなければいけなくなるので、

? NO SPACE SKIP ? (sequence内とその配下で有効)の他に特殊文字列を追加し、

? SEQUENCE SEPARATOR IS (NOT) REQUIRED? 指定で、識別子に空白OK(NO)にすれば良いかな?

ps.2024/4/8

やっと数値を右揃えで表示。論理式は数式のみ。’xxxxxで文字列をEBNFに追記。

? SEQUENCE SEPARATOR IS (NOT) REQUIRED?の方は組み込んでみた。

実装の都合上、branchパーサを追加

/**
  * 判定式の結果で処理を分岐するパーサを作成する
  * @param {Function} expr           判定式
  * @param {Function} trueParser     結果が真の場合に実行するパーサ
  * @param {Function} falseParser    結果が偽の場合実行するパーサ
  * @returns 
  */
 branch(expr, trueParser, falseParser) {
     const methodName = 'branch';
     trueParser = this.normalizeParses(trueParser);
     falseParser = this.normalizeParses(falseParser);
     this.chkFunction(methodName, "trueParser", trueParser);
     this.chkFunction(methodName, "falseParser", falseParser);
     const func =
         /**
          *  生成した処理を分岐するパーサ
          * @param {string} target       ソースコード
          * @param {integer} position    読取り位置
          * @return {ParseInfo}          パースした結果
          */
         (target, position) => {/*branch*/
             if (expr()) {
                 return trueParser(target, position);
             } else {
                 return falseParser(target, position);
             }
         }
     return func;
 }
const identifier = _map(
    _branch(() => { return this.sequnceSeparator }, /[A-Za-z][-A-Z_a-z0-9\x20]*/, /[A-Za-z][-A-Z_a-z0-9]*/)
    , (parsed) => {
        parsed = parsed.trim();
        parsed = parsed.replaceAll(/\x20{2,}/g, '\x20');   //空白が並んでいた場合は1つにまとめる
        return { '#identifier': parsed };
    });
const sequnce = _map(
    wraper, _repeate(_branch(() => { return this.sequnceSeparator }, ",", _option(",")), wraper, 0)
    , (parsed) => {
        const methodName = "sequnceのfn";
            ・・・

この程度の処理でもmapのsequenceパラメータ部に分岐処理を詰め込む必要があったのでパーサにしないといけないが、このままでは「?特殊文字列?」の機能はEbnfParser限定になってしまう。後、?と?の間の空白は両端をTrimし連続する空白は1個にまとめている。

?テスト?

サンプル = ?=テスト?, "TEST", "NOW" | "TEST" ;    ☚上で?テスト?を宣言すると?=テスト?がtrue,'テスト'を返すとか?

のような使い方ができると分岐も書けて便利かな?特殊文字をコメントっぽく扱っているので、ちゃんと文字列あるいは何かのリテラルとして扱える様にしないとダメかもしれない。EBNFの*演算子は正規表現っぽく使わないらしいのでこの辺を見直さないといけないかな?例外’ー’の意味が判らなかったが、集合の差らしい。つまり

集合の積 = A , B , C ;        (* A 且つ、 B 且つ、 C である *)
集合の和 = A | B | C ;        (* A または、B または、C である *)
集合の差 = A - B - C ;        (* A である、B は除外、C も除外 *)

っぽい考えらしい。実際には EBNFでは集合の積より順序の方が意味が強い、また最後のは、(* A である、B は除外、でも C は含む *)とも読めるけど、面倒そうだから気のせいにして、実装した。

/**
 *  例外(集合の差)パーサを作成する
 * @param {Array of parser} parsers     例外(集合の差)するパーサの配列
 * @return {Function} 					生成した連結パーサ
 */
exception(...parsers) {
    const methodName = 'exception';
    // パーサに正規表現やテキストが混ざっていたらパーサに変換する
    parsers = this.normalizeParses(parsers);
    this.chkFunction(methodName, "parsers", parsers);
    // パーサが単体の場合はそのまま返す
    if (parsers.length === 1) {
        this.chkFunction(methodName, "parsers[0]", parsers[0]);
        return parsers[0];
    }
    // パーサが複数の場合は、全ての判定が真の場合に成功を返すパーサを渡す
    const func =
        /**
         *  生成した例外(集合の差)パーサ
         * @param {string} target       ソースコード
         * @param {integer} position    読取り位置
         * @return {ParseInfo}          パースした結果
         */
        (target, position) => {/*sequnce*/
            const bk = this.fSpaceSkip;
            if (this.fSpaceSkip) { [target, position] = this.skipSpace(target, position); }
            const oldPosition = position;
            let result;
            for (let i = 0; i < parsers.length; i++) {
                const parser = parsers[i];
                this.chkFunction(methodName, "parser", parser);
                const parsed = parser(target, position);
                if (parsed === undefined) {
                    continue;
                }
                if ((i === 0 && parsed.success) || (i !== 0 && !parsed.success)) {
                    if (i === 0) {
                        result = parsed.result;
                    }
                } else {
                    parsed.position = oldPosition;
                    // 一つでも判定で偽となれば、このパーサ自体が失敗を返す
                    parsed.success = false;
                    this.fSpaceSkip = bk;
                    return parsed;
                }
            }
            this.fSpaceSkip = bk;
            return new ParseInfo(true, result, position);
        };
    return func;
}

sequenceと違うのは名前とtrue,falseの判定の仕方だけ

「-」を演算子に使うので非終端記号(識別子)に「-」は使えなくなったのが地味に痛い。ここにもあっちにも「-」が散乱してたので修正ががが。

セルの数式をトークン化した影響でJSONファイルをアップロードするとセルに数式が入らない、日付がテキストに化けていたのを修正。テキストファイルのアップロードも。

ps.2024/4/11

数式のセル参照がセル範囲形式の場合に先頭と最終セルを格納していたので中間のセルの値が変わっても反応していなかった。マクロとJDOCと正規表現のEBNF構文を模索中。

  • 今も正規表現を正規表現でちゃんと表現していないので時々通らない事がある
    • とりあえずEBNF化して解析できるようにしたい。
  • javascriptっぽいマクロが書けるようにしたい
    • 組み込み関数もマクロで書いた方が楽そうだから
      • でも、全く型無し支援無しは辛いのでJDOCをマクロ構文に盛り込みたい
        • JDOCもEBNF化したい
          • このマクロのEBNFを1つのBNFで書けない事はないがコピペだらけになる
            • 各々のEbnfParser派生クラスで作成したparserを参照できるようにしたい。
              • 作成する順番に依存すると大変
                • lazy的な参照の仕組みにしよう
  • EbnfPaser ,ExprParser, ( MacroParser, JdocParser }
    • RegexpParserは最終結果をEbnfParserのparser.regexpのパターンに組込む
      • &:EBNFのパターンを空白無しで処理する演算子に正規表現も組み込めそう
        • “/” + /.*/ + “/” 的なものが予想通りな結果になったら嬉しい
  • パーサ作成部(makeParser)とパーサ実行部(parse)に分離
    • 各々のコンストラクタで自分の構文を事前にパーサ作成させる
      • makeParserとparserは各々継承させる

ps.2024/4/12

パーサ作成処理内でテストパターンを処理させるようにしたら、ほとんどの数式がテキスト扱い。数式のEBNFはあちこちに*を付けていたが目障りで何となく削除したせいだった。真の原因は肝心の{…}のパーサの処理がリピートしておらず実質choiceになっていて、ごく一部の2つのパラメータを持つ関数や1+2などは数式として扱っていたものの、=1とかはテキストになっていた。関数のパラメータが無い場合の扱いが曖昧だった。

(*$ SEQUENCE SEPARATOR IS NOT REQUIRED $*)
(* セルのデータ *)
cell                = expr_cell | value_cell ;
(* 値 *)
value_cell          = time_stamp | date | time | number | boolean | sqstring | text_etc ;
time_stamp          = date ' ' time ;
date                = yyyy '/' MM '/' dd  | MM '/' dd ;
yyyy                = /\\d{4}/ ;
MM                  = /\\d{1,2}/ ;
dd                  = /\\d{1,2}/ ;
time                = HH ':' mm ':' ss | HH ':' mm  ;
HH                  = /\\d{1,2}/ ;
mm                  = /\\d{1,2}/ ;
ss                  = /\\d{1,2}/ ;
number              = /[-]?([0-9]+)([.][0-9]*)?/ ;
boolean             = /true/i | /false/i ;
sqstring            = /'.*/ ;
text_etc            = /.+/ ;
(* 数式 *)
expr_cell           = '=' expr ;
expr                = logical_expr ;
logical_expr        = add_sub_expr [ ( '=' | '>' | '<' | '>=' | '<=' | '<>' ) add_sub_expr ] ;
add_sub_expr        = mul_div_expr { ( '+' | '-' | '&' ) mul_div_expr } ;
mul_div_expr        = factor { ( '*' | '/' ) factor } ;
factor              = value | parenthesis_expr ;
parenthesis_expr    = '(' expr ')' ;
value               = number | boolean | function_def | a1_range | a1 | dqstring ;
a1_range            = a1 ':' a1 ;
a1                  = /([A-Za-z]+[1-9][0-9]*)/ ;
dqstring            = /"[^"]*"/ ;
function_def        = symbol '(' parameters ')' ;
symbol              = /([A-Za-z][A-Za-z0-9_]*)/ ;
parameters          = [ expr ] { ',' expr } ;

それにしても、さっきまでparameters = expr { ‘,’ expr } ;で動いていたのはなぜなんだろう。

  • 循環計算をescキー入力で中断させたい。
    • 数式セルに計算回数(count)を追加し、どこかでセルの内容を手入力で書き換えたらリセットして再計算を開始、セル値が更新したら+1し、10,100,1000,10000になったら、中止確認メッセージ
    • どこかでセルの内容を手入力で書き換えたら、再計算開始時刻をリセットし、10秒たっても再計算が完了しなかったら、「タイムオーバー」

ps.2024/4/13

繰返し演算子に正規表現の{n,m}を追加したところ、パーサが長く読みにくいので、呼出部(parser.js)は今まで通りにパラメータの調整やチェックの後、基本部(coreParser.js)で生成されたパーサに空白読飛し等をラップする。

・どうやってもログのクラス名(this.className)が派生クラス名になる。

ps.2024/4/14

EBNFのパーサを微調整。repeat symboleを囲み文字やコメントのみにした。継承クラス同士で同じメソッド名になるがどうしても自分のクラスのメソッドを呼びたいケースがあるのでローカルメソッド名(#xxxx)を併用。

xxxx(a,b,c) { // 継承先のクラスのメソッドを呼びたい場合に使用する
  return this.#xxxx(a,b,c);
}
#xxxx(a,b,c) { // 自分のクラスのメソッドをどうしても呼びたい場合に使用する ※makeParser, parse, selfTest, etc.
 本体
}

ps.2024/4/15

でも、全ソースで同じ内容の#メソッドを書くのは、後々面倒なので、

・・・各派生クラスのconstructorに以下をコピペして
this.selfTest(
    this.#getSelfTestList(), this.#parser,
    {
        className: 'EbnfParser',
        testLog: false, jsonLog: false, mapLog: false,
        parse: this.#parse.bind(this)
    }
);
と呼び出し、

・・・以下、基底クラスで、実行させる
/**
 * 作成したパーサをテストする
 * @param {Function} parser 
 * @param {string} testText 
 * @param {object} info 
 * @returns 
 */
selfTest(testText, parser, info) {
    const methodName = 'selfTest';
    if (Array.isArray(testText)) {
        const stack = testText.map((t, index, a) => {
            if (info) {
                console.log(`${info.className}.${methodName}: pattern[${index + 1}/${a.length}]`);
            }
            return this.selfTest(t, parser, info);
        });
        return stack;
    } else {
        if (info && info.testLog) {
            this.map.DEBUG = info.mapLog;
            console.log(`${info.className}.${methodName}: text:'${testText}'`);
        }
        const result = info.parse(testText, parser);
        if (info.jsonLog) {
            this.jsonStringifyLog(result);
        }
        return result;
    }
}

と、ローカルなメソッドをパラメータに列挙してかいくぐることにした。※いつかシンタックスなエラーになりそうだけど。

ps.2024/4/16

EBNFテキストはセルフサンプル用なので色々変更しても、数式など他のEBNF文法に影響を与えないので、どうでもいいのだけれど・・・EBNFテキストで「repeate='{‘, sequence, ‘}’」と修正した。実際のEBNFパーサrepeateの実装に揃えた形。なぜsequenceになったのか?文法の繰り返しで多用する「'{‘ 演算子, 非終端記号 ‘}’」を書くたびにrepeate(sequence(a,b,c}}と毎度sequenceを書くのが面倒で、多用する方に実装方法を寄せました。

それよりも

記事中のsequnceに波線が付いていたけど・・・sequence not found.のエラーメッセージの影響で、EBNFのsequenceの綴りを訂正。いっぱい間違っていたので、修正漏れとか修正ミスとかでサッパリ動かず。

&演算子の結果がundefinedだった。式が未入力のセルを参照した場合は数値の0として扱う。



[ストレージ]SSDとHDD

SSDの方が速いのは知っている。

SSDからHDDに100GBのデータを移行している時のスクリーンショットがコレ。

メモリの使用量が変わんないからメモリキャッシュを使わないでファイルを移動している様だ。

キャッシュ処理なんか無い方がプログラムは簡単に済むけど、HDDって磁気ヘッドの移動の手間がかかるので、とりあえずメモリをキャッシュ代わりに使って磁気ヘッドの移動中の待ち時間を少なく見せる努力はしたとは思うけど、OSがHDDに物理的にアクセスしているつもりでもHDDの大容量化で内側と外側のトラックでセクタ数が異なりHDDがOSからのヘッド、シリンダ、セクターのパラメータを適当な値に変換する様になったからOSからHDDのヘッドの状況が掴めそうもないから、やめちゃったのかもしれない。そのせいか、HDDへのファイルコピー操作をいくつも並行して流すと、さっぱり進まない。



[javascript]spread-sheetカスタムエレメントその2

EBNF解析用に書き換えた組み込みパーサをスプレッドシートに移植できたので、次はスプレッドシートの数式の文法をENBF風に書いてみると

(* セルのデータ *)
cell                ::= expr_cell | value_cell ;
(* 値 *)
value_cell          ::= time_stamp | date | time | logical_expr | number | boolean | text_etc ;
time_stamp          ::= date ' ' time ;
date                ::= yyyy '/' MM '/' dd  | MM '/' dd ;
yyyy                ::= /\\d{4}/ ;
MM                  ::= /\\d{1,2}/ ;
dd                  ::= /\\d{1,2}/ ;
time                ::= HH ':' mm ':' ss | HH ':' mm  ;
HH                  ::= /\\d{1,2}/ ;
mm                  ::= /\\d{1,2}/ ;
ss                  ::= /\\d{1,2}/ ;
number              ::= /[-]?([0-9]+)([.][0-9]*)?/ ;
boolean             ::= /true/i | /false/i ;
text_etc            ::= /.+/ ;
(* 数式 *)
expr_cell           ::= '=' expr ;
expr                ::= logical_expr ;
logical_expr        ::= add_sub_expr [ ( '=' | '>' | '<' | '>=' | '<=' | '<>' ) add_sub_expr ] ;
add_sub_expr        ::= mul_div_expr { ( '+' | '-' | '&' ) mul_div_expr }* ;
mul_div_expr        ::= factor { ( '*' | '/' ) factor } ;
factor              ::= value | parenthesis_expr ;
parenthesis_expr    ::= '(' expr ')' ;
value               ::= number | boolean |  function_def | a1_range | a1 | dqstring ;
a1_range            ::= a1 ':' a1 ;
a1                  ::= /([A-Za-z]+[1-9][0-9]*)/ ;
dqstring            ::= /".*"/ ;
function_def        ::= symbol '(' parameters ')' ;
symbol              ::= /([A-Za-z][A-Za-z0-9_]*)/ ;
parameters          ::= expr { ',' expr } ;

ここでoption( […])の中身をsequenceとしているので、EBNF解析パーサもchoiceからlistに変更してドッチでも良しのつもりだったが、”(…) 値 ”の様な場合は、(…)でchoice様に当確判定が出てしまい(値)ではなく即(])判定に走ってミスとなったので、sequenceに変えた。※EBNFのlist を sequence | choice; に変えようかなとも思ったがchoiceが一切通らなくなる様な気がする。

よくよく考えてみると文法の定義(defintion)文はsequenceが基本で、文法上sequenceを明示する書式がなかったりする。「,」で区切れば可能だが、何かありそう。

{
  "cell": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_identifier": "expr_cell" }
          ]
        },
        {
          "_sequence": [
            { "_identifier": "value_cell" }
          ]
        }]
    }}},
{
  "value_cell": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_identifier": "time_stamp" }
          ]
        },
        {
          "_sequence": [
            { "_identifier": "date" }
          ]
        },
        {
          "_sequence": [
            { "_identifier": "time" }
          ]
        },
        {
          "_sequence": [
            { "_identifier": "logical_expr" }
          ]
        },
        {
          "_sequence": [
            { "_identifier": "number" }
          ]
        },
        {
          "_sequence": [
            { "_identifier": "boolean" }
          ]
        },
        {
          "_sequence": [
            { "_identifier": "text_etc" }
          ]
        }]
    }}},
{
  "time_stamp": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_identifier": "date" },
            { "_sq_string": " " },
            { "_identifier": "time" }
          ]
        }]
    }}},
{
  "date": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_identifier": "yyyy" },
            { "_sq_string": "/" },
            { "_identifier": "MM" },
            { "_sq_string": "/" },
            { "_identifier": "dd" }
          ]
        },
        {
          "_sequence": [
            { "_identifier": "MM" },
            { "_sq_string": "/" },
            { "_identifier": "dd" }
          ]
        }]
    }}},
{
  "yyyy": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_regexp": "/\\d{4}/" }
          ]
        }]
    }}},
{
  "MM": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_regexp": "/\\d{1,2}/" }
          ]
        }]
    }}},
{
  "dd": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_regexp": "/\\d{1,2}/" }
          ]
        }]
    }}},
{
  "time": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_identifier": "HH" },
            { "_sq_string": ":" },
            { "_identifier": "mm" },
            { "_sq_string": ":" },
            { "_identifier": "ss" }
          ]
        },
        {
          "_sequence": [
            { "_identifier": "HH" },
            { "_sq_string": ":" },
            { "_identifier": "mm" }
          ]
        }]
    }}},
{
  "HH": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_regexp": "/\\d{1,2}/" }
          ]
        }]
    }}},
{
  "mm": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_regexp": "/\\d{1,2}/" }
          ]
        }]
    }}},
{
  "ss": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_regexp": "/\\d{1,2}/" }
          ]
        }]
    }}},
{
  "number": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_regexp": "/[-]?([0-9]+)([.][0-9]*)?/" }
          ]
        }]
    }}},
{
  "boolean": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_regexp": "/true/i" }
          ]
        },
        {
          "_sequence": [
            { "_regexp": "/false/i" }
          ]
        }]
    }}},
{
  "text_etc": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_regexp": "/.+/" }
          ]
        }]
    }}},
{
  "expr_cell": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_sq_string": "=" },
            { "_identifier": "expr" }
          ]
        }]
    }}},
{
  "expr": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_identifier": "logical_expr" }
          ]
        }]
    }}},
{
  "logical_expr": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_identifier": "add_sub_expr" },
            {
              "_option": [
                {
                  "_sequence": [
                    {
                      "_group": [
                        {
                          "_sequence": [
                            { "_sq_string": "=" }
                          ]
                        },
                        {
                          "_sequence": [
                            { "_sq_string": ">" }
                          ]
                        },
                        {
                          "_sequence": [
                            { "_sq_string": "<" }
                          ]
                        },
                        {
                          "_sequence": [
                            { "_sq_string": ">=" }
                          ]
                        },
                        {
                          "_sequence": [
                            { "_sq_string": "<=" }
                          ]
                        },
                        {
                          "_sequence": [
                            { "_sq_string": "<>" }
                          ]
                        }]
                    },
                    { "_identifier": "add_sub_expr" }
                  ]
                }]
            }]
        }]
    }}},
{
  "add_sub_expr": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_identifier": "mul_div_expr" },
            {
              "_repeate0": [
                {
                  "_sequence": [
                    {
                      "_group": [
                        {
                          "_sequence": [
                            { "_sq_string": "+" }
                          ]
                        },
                        {
                          "_sequence": [
                            { "_sq_string": "-" }
                          ]
                        },
                        {
                          "_sequence": [
                            { "_sq_string": "&" }
                          ]
                        }]
                    },
                    { "_identifier": "mul_div_expr" }
                  ]
                }]
            }]
        }]
    }}},
{
  "mul_div_expr": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_identifier": "factor" },
            {
              "_repeate0": [
                {
                  "_sequence": [
                    {
                      "_group": [
                        {
                          "_sequence": [
                            { "_sq_string": "*" }
                          ]
                        },
                        {
                          "_sequence": [
                            { "_sq_string": "/" }
                          ]
                        }]
                    },
                    { "_identifier": "factor" }
                  ]
                }]
            }]
        }]
    }}},
{
  "factor": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_identifier": "value" }
          ]
        },
        {
          "_sequence": [
            { "_identifier": "parenthesis_expr" }
          ]
        }]
    }}},
{
  "parenthesis_expr": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_sq_string": "(" },
            { "_identifier": "expr" },
            { "_sq_string": ")" }
          ]
        }]
    }}},
{
  "value": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_identifier": "number" }
          ]
        },
        {
          "_sequence": [
            { "_identifier": "boolean" }
          ]
        },
        {
          "_sequence": [
            { "_identifier": "function_def" }
          ]
        },
        {
          "_sequence": [
            { "_identifier": "a1_range" }
          ]
        },
        {
          "_sequence": [
            { "_identifier": "a1" }
          ]
        },
        {
          "_sequence": [
            { "_identifier": "dqstring" }
          ]
        }]
    }}},
{
  "a1_range": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_identifier": "a1" },
            { "_sq_string": ":" },
            { "_identifier": "a1" }
          ]
        }]
    }}},
{
  "a1": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_regexp": "/([A-Za-z]+[1-9][0-9]*)/" }
          ]
        }]
    }}},
{
  "dqstring": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_regexp": "/\".*\"/" }
          ]
        }]
    }}},
{
  "function_def": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_identifier": "symbol" },
            { "_sq_string": "(" },
            { "_identifier": "parameters" },
            { "_sq_string": ")" }
          ]
        }]
    }}},
{
  "symbol": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_regexp": "/([A-Za-z][A-Za-z0-9_]*)/" }
          ]
        }]
    }}},
{
  "parameters": {
    "_definition": {
      "_choice": [
        {
          "_sequence": [
            { "_identifier": "expr" },
            {
              "_repeate0": [
                {
                  "_sequence": [
                    { "_sq_string": "," },
                    { "_identifier": "expr" }
                  ]
                }]
            }]
        }]
    }}}

「パーサコンビネーションでの実装を諦める」方向に進みそうな雰囲気になってきたが、パーサコンビネーションの方がデバッグしやすいと云う妄想が消え去った後ではコレで良いのかもしれない。

正規表現のオプションの記述が抜けている方が気になるが、空白や改行は基本的に無視する数式文法なので問題は無いだろう。

完璧なテストパターンが欲しくなってきたが、まだまだ内部実装は変わりそうなので諦め。




top