計算式Editorの作り方

この記事はHRBrain Advent Calendar 2022の19日目の記事です。

qiita.com

はじめに

HRBrainではタレントマネジメントシステムHRBrainを提供しています。

社員名簿機能では、運用に合わせて自由に社員情報に紐付く項目を作成・編集することで、あらゆるデータを管理できます。

項目には種類があり、文字列や数値、カスタムプルダウン、計算式など項目の種類も選ぶことが可能です。

レイアウト編集画面(このレイアウト編集画面も一年半ほど前に自分が実装を担当しました。)

計算式項目はさまざまな用途に利用されています。

例えば生年月日から年齢を算出したい場合の計算式は下記の通りです。

ISBLANK({生年月日}) ? '' : DATEDIF({生年月日}, NOW(), 'Y')

計算式の言語仕様は下記をサポートしています。

  • ISBLANKDATEDIFなどの関数
  • 社員名簿に登録されている他の項目からの参照
  • 数値や文字列
  • +-などの演算子
  • 三項演算子

2022年12月現在、計算式を設定する際はシンプルなInput要素に文字列を入れていただく形となっています。 簡単な計算式であればそれで十分だったのですが、想像を超えて複雑な計算式を利用されるようになり、計算式Editorの必要性が増しました。

現在の計算式入力画面(一部省略しています)

それを受け、今回計算式Editorを作ろうと思い立ち、計算式Editorを作るために行ったことをまとめて行こうと思います。

作り方

1. Tokenizeを行う

今回のように計算式をTokenizeを行う場合は演算子や数値、関数などのトークン(単語や文字の塊)に分割することを指します。 例えば、「12 + 3 * 4」という数式の場合、トークンは「12」「+」「3」「*」「4」という演算子や数値の単位に分割されます。

先程の年齢を算出するための計算式の場合は下記の画像のように分割されます。

今回は分割する際に値と合わせて、トークンの種別も持つようにしました。

トークンの種別は下記の5種類定義しました。

Number

数値型 整数や小数を扱う。

String

文字列型 ダブルクォーテーションもしくはシングルクォーテーションで括られた文字列

Operator

演算子 +-などや三項演算子のための?:など、また比較のための==>なども受け入れます。

Bracket

括弧 ()や[]などの括弧を扱います。

Identifier

上記以外の文字列は何かしら意味があるものであることが多いので識別子として扱います。 今回だと関数名や生年月日などの参照項目名などが入ってきます。

実装については文字列を先頭から1文字ずつ取り出し塊ごとに分割していきます。

2. 抽象構文木にParseする

Tokenizeでトークンごとに分割されましたが、それだけでは括弧の数があっているのかなどのFormatとして正しいのかなどが不明です。

そこで抽象構文木に変換することで、計算式の構造を理解しやすくします。

こちらもトークンを前から一つずつとっていきIdentifierの次に「Bracket(()」があったら関数として扱うのように判別しながら木構造に変換していきます。

先程同様に年齢を算出するための計算式はこのような木構造に変換されます。(開始位置、終了位置等を省略しています)

使われている関数や引数などが理解しやすい形になりました。

3. Validationを行う

数式のFormatとして大枠が正しいことが木構造に変換することで確認できました。ただしこのタイミングでは実在しない関数名を指定できたり、「生年月日」の代わりに存在しない「誕生日」という参照項目を誤って書いてしまうことも可能な状態です。

そこで存在する関数名なのか、また関数の引数の数や受け入れる形式(数字なのか文字列なのか)、存在する項目を参照できているかなどのチェックを行います。

4. Editor画面を作ろう

Editorを作る前に実現したいことを洗い出しました。

  • 計算式のFormatが正しくないときにエラー内容を提示する
  • 関数や参照項目の入力を簡単に行えるようにする
  • 関数の説明文や引数に何を指定すべきなのかがわかりやすい
  • 参照項目を少し目立たせたい

1から作ることも考えたのですが、入力をサジェストする機能を作るにあたって、テキストエリア内のスタイルを調整したり、キャレット(カーソル)の位置を気にしたりすることを考えると大変そうだったのでライブラリーを利用することを考えました。

下記の3つのライブラリーを検証しました。

調査した結果、interfaceがシンプルで使いやすかったrich-textareaを採用することに決めました。

rich-textareaではキャレットの位置のPosition(x,y)や何文字目なのかを取ることができます。

キャレットが何文字目なのかと先程までの木構造と組み合わせることで、今どの値を書く位置にいるのか判別できるようになり、あとは力技でサジェストができるようになりました。

またrich-textareaではrendererを実装することができ、用意されているcreateRegexRendererを利用することで任意の文字列に対して任意のスタイルを与えることが可能になっています。

const renderer = createRegexRenderer([
  [/red/g, { color: "red" }],
  [/blue/g, { color: "blue" }],
]);

これらを組み合わせた結果下記のような計算機Editorが完成しました。

最後に

こちらの計算機Editorは技術検証を終えたタイミングであり、プロダクションコードにまだ入れきれておりません。 近々もう少し作り込んだものをリリースできればと考えております。 文字列を抽象構文木に変換することによってフロントエンドとして扱いやすく、やりたいことがやれる状態にできたと考えています。

今後も顧客により使いやすいサービスになれるよう努めていきたいと思います。

またHRBrainのエンジニアリングに興味を持っていただいた方はぜひ弊社の採用ページもご覧ください。

www.hrbrain.co.jp