react-gridsheetで数式をサポートしました

2022/05/09に公開

お久しぶりです。しばらく更新をサボっていましたが、ようやく数式をサポートしました。
GWの後半半分くらいをこれに使ってしまいました。本当に何をやっているんでしょうか🙄

サンプル

いきなり技術的な話をするのもあれなので、ひとまずサンプルです。

そのほかのサンプルは こちら

(何故かcodesandbox埋め込みだとセルの範囲がドラッグできなくなっているので直接見に行ってください...)

HLOOKUPでランクを求めたり条件に一致するものを数えたりするだけのサンプルですが、数式として正しく評価できているのがわかりますね。

現状で使える関数は以下です。
https://github.com/walkframe/react-gridsheet/tree/master/src/formula/functions

全然足りていないので開発者が独自に追加できるようにしています。G4セルの HOPE 関数は私が適当に追加した関数です。

ちゃんとテストを書いたらPRを受けられるようにしたいです。

少しだけ技術的な話

数式を評価する流れとして「字句解析」によって得られたトークンを「構文解析」にかけてASTという木構造にしてから評価しています。
字句解析はそれほど難しくないですが、構文解析は再帰的に処理をしたり演算子の優先順位を考慮する必要があったりして大変でした。しばらく触りたくないです😇

ASTというのがちょっとイメージしにくいかもしれないので例を出してみます。
= (1 + 2 * 3) + 4 + sum(5 * 6, 7 - 8) という数式を構文解析するとこんな感じになります。

add(
  add(
    add(1, multiply(2, 3)),
    4,
  ),
  sum(
    multiply(5, 6),
    minus(7, 8),
  )
)

+などの中置演算子も関数の形式になり、必ず外側は一つの式になります。木構造なので当然ですがちょっと面白いですね。

詳しいコードを見たい方は以下を参照してください。

https://github.com/walkframe/react-gridsheet/blob/master/src/formula/evaluator.ts

本当は lexer, parser, evaluator でファイルを分けたかったんですが、循環参照になってしまってたので1ファイルになりました😜

課題

数式をサポートしたことで少し表計算っぽくなりましたが、まだ使いづらいと思います。
理由は3つあります。

セルの入力補助が効かない

通常は数式の入力モードになるとセル内のエディタが構文を理解して入力の補助をしてくれたり、範囲を入力する箇所ではマウスドラッグしたセルを範囲として入力してくれます。

このライブラリではセルの入力欄はただのテキストエリアなので上記のようなリッチなサポートは現状できません。とはいえ将来的にはサポートしたいと思っています。

オートフィルが使えない

そのまんまです。そして絶対参照もパースできません。
これも将来的にはサポートしたいと思っています。

行列の追加削除で自動的に参照が変わらない

通常、行列の追加削除が発生したときに参照しているセルが影響を受ける場合、自動的にその参照を書き換えてくれます。

現状サポートしていないわけですが、単純に実装すると懸念があります。
例えば、行の削除が生じた場合にそれ以下にあるセルの影響のある数式をすべて書き換えなければいけません。セルが多くなればこれは結構な実行コストになります。

とはいえこれだけであればまだよいのです。問題は履歴です。
このライブラリはUndo,Redoをサポートしているので、操作による変更差分を履歴として持っています。行の削除で参照がずれるセルが1万セルあったら、1万セル分の変更履歴がメモリに乗ります。単なる行の削除でそのセルに直接の影響がないにもかかわらず、です。

これは結構致命的な問題かつ、今のデータ構造では厳しいかもしれないと思っています。

対応として、以前の記事で書いたデータ構造に戻し、参照を一意なアドレスとして保持するのが良さそうな気がしています。
https://zenn.dev/righ/articles/f2415150f1baab#これまでの構造

たとえば、 =countif(B5:B9, ">" & H7) のような数式が入力されたら内部的には =countif(#100:#120, ">" & #500) のようにアドレスで保持して、ユーザに見せるときに元の参照に戻してあげるということです。こうすれば追加削除で参照がずれることはありません。

正直この問題にはもっと早い段階で気づけたはずだなーと反省しています。

実際のスプレッドシートはどうやっているんでしょうか。わたし、気になります!

これからもすこしずつがんばるので応援よろしくおねがいします!!⭐
https://github.com/walkframe/react-gridsheet

Discussion