-
Notifications
You must be signed in to change notification settings - Fork 186
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AddDice::Parser #147
AddDice::Parser #147
Conversation
bf22132
to
fe4845f
Compare
Codecov Report
@@ Coverage Diff @@
## master #147 +/- ##
==========================================
+ Coverage 86.15% 86.16% +0.01%
==========================================
Files 198 204 +6
Lines 21013 22044 +1031
==========================================
+ Hits 18103 18995 +892
- Misses 2910 3049 +139
Continue to review full report at Codecov.
|
bf793fb
to
9ca80b5
Compare
9ca80b5
to
e3921d1
Compare
処理結果は問題なさそうです。 |
新しく追加したファイルについて、AddDiceの子クラスが書かれているファイルを src/dice/add_dice/ 以下に置くのはいかがでしょうか? クラスの階層と位置が対応するので、ファイルを探しやすくなります。また、今後のv3でのディレクトリ構成とも近くなります。 |
全体を見て、今回はこれで問題ないと思いました。現段階ではNodeがAddDice内で完結しており、状況によってeval処理が変わることがないためです。 |
refs #147 仕様変更をすべてカバーするように、テストケースを追加する。 これまでカバーされていなかった仕様は、以下のとおり。 * 1. 目標値に四則演算を含めることができる(`1d6>=1+2` 等) * 4. 負数の割り算ができる(`1d6/-3` 等) * 5. 定数部がある場合、途中式が省略されない * 旧:`(1D6+1) > 5` * 新:`(1D6+1) > 4[4]+1 > 5` 以上に加えて、ダイス1個、定数部なしの場合に最終結果のみが表示される こと(例:`(1D6) > 5`)を確認するテストケースを追加する。
refs #147 仕様変更をすべてカバーするように、テストケースを追加する。 これまでカバーされていなかった仕様は、以下のとおり。 * 1. 目標値に四則演算を含めることができる(`1d6>=1+2` 等) * 4. 負数の割り算ができる(`1d6/-3` 等) * 5. 定数部がある場合、途中式が省略されない * 旧:`(1D6+1) > 5` * 新:`(1D6+1) > 4[4]+1 > 5` 以上に加えて、ダイス1個、定数部なしの場合に最終結果のみが表示される こと(例:`(1D6) > 5`)を確認するテストケースを追加する。
AddDice::Parser: テストケースを追加する
Co-Authored-By: ocha <ochaochaocha3@gmail.com>
構文解析のデバッグを行いやすくするため
AddDice::Node: ノードのS式を返すメソッドを追加する
減算を含む場合に、項の順番に関係なく定数畳み込みの結果が等しくなる ことを確認する、テストケースを追加する(現状では失敗する)。
定数畳み込み(式の簡約化)について、2週間ほど調査や試実装をしてみましたが、抽象構文木を作る場合には、単純で安定した実装にするのがなかなか難しいと分かりました。そのため、これは一旦保留にする(アイデアとして採用しておき、後に詳細な仕様を決めて実装する)のはいかがでしょうか? 現在、テストケースには乗算のみ簡約化するものがあります(https://github.com/bcdice/BCDice/blob/v2.04.00/src/test/data/dummyBot.txt#L96-L100; |
式の簡約化をまじめに実装する場合に参考になる教科書が見つかりましたので、参考として紹介します。 Joel S. Cohen: "Computer algebra and symbolic computation: mathematical methods", A K Peters (2002). この教科書の3.2節に、各演算の代数学的性質を利用して式を簡約化するアルゴリズムが載っています。十分に正確ですが、再帰を含むかなり複雑なアルゴリズムとなっています。核の部分の一部を簡易に実装した例をGistに上げたので、確認してみてください。 |
定数畳み込みの失敗例
…ing_test_case AddDice: 定数畳み込み関連のテストケースを追加する
|
@times = times.literal | ||
@sides = sides.literal | ||
@critical = critical.nil? ? nil : critical.literal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DiceRollのみ、オペランドを生の数値で持っていますが、何か特別な理由はあるのでしょうか?
他の演算子のようにオペランドをノードとして持っておくと、eval
や s_exp
などの処理が同じように書ける(それぞれ eval
や s_exp
を再帰的に適用する形)ので、自然です。
また、このようにインターフェースを合わせておくことで、構文解析処理が変わって times, sides, critical
が Number
でなくなっても、書き直さずに対応できるという利点があります。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DiceRollがリテラルだからです。 nDx
は一見二項演算のように振る舞えますが、n
やx
にDiceRollが入れ子になると、ダイス結果を展開した中間式を表現することができなくなります。そのため、二項演算と扱うことはできません。
これらの前提から、n
やx
は乱数を含まない、もしくは整数の演算のみで事前計算ができるはずなのでノードの生成時に引数を整数に変換して問題ないと思います。かえって、ノード生成時に整数に変換できるという制約をいれることで、DiceRollが入れ子になる状況を避けることができます。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ノード生成時に整数に変換できるという制約をいれる
これは単純にコンストラクタで型チェックを行った方が分かりやすいように思いました:
def initialize(times, sides, critical)
unless times.is_a?(Number)
raise TypeError, "times must be a Number: #{times.class}"
end
unless sides.is_a?(Number)
raise TypeError, "sides must be a Number: #{sides.class}"
end
# ...
end
n
や x
を整数と見なせることは文法(と括弧内前処理)から分かるので、変換を先に行っても大丈夫なことには納得です。
個人的には、変換も #literal
(Numberの内部表現、private
でも問題ない)ではなく #eval
(評価の公開インターフェース)で行う方がいいかな、と思います。
その方がNumberへの修正に強いです。
今のところ #eval
で整数が返るのが保証されているので、型の面でも問題ないです。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#eval
は引数にランダマイザを要求するため、コンストラクタで単純に使うことはできません。なので、引数の型だけチェックしようと思いますが、どうでしょう。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
今のところは、文法からダイスロールのオペランドが整数になるのは確定するので、型チェックはそれで代用するのでもいいかな、と思いました。
除算ノードのクラスを作り、BinaryOpを単純化する。 除算の処理だけが特別なので、汎用的なBinaryOpでその面倒を見るのは重い。 そこで、端数処理方法別に除算ノードのクラスを3つ作り、それらに処理を 分担させる。 継承関係: BinaryOp(二項演算子) | +-- DivideBase(除算の基底クラス) | |-- DivideWithRoundingUp (除算 -> 切り上げ) |-- DivideWithRoundingOff (除算 -> 四捨五入) +-- DivideWithRoundingDown(除算 -> 切り捨て)
ROUNDING_METHOD_SYMBOL では、どうしても Symbol が入っているのを 連想してしまうので。
AddDice::Node: 二項演算子をリファクタリングする
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
大きな変更でしたが、これで大丈夫だと思います。いろいろと意見を取り入れていただき、ありがとうございました。
@ochaochaocha3 |
refs #147 仕様変更をすべてカバーするように、テストケースを追加する。 これまでカバーされていなかった仕様は、以下のとおり。 * 1. 目標値に四則演算を含めることができる(`1d6>=1+2` 等) * 4. 負数の割り算ができる(`1d6/-3` 等) * 5. 定数部がある場合、途中式が省略されない * 旧:`(1D6+1) > 5` * 新:`(1D6+1) > 4[4]+1 > 5` 以上に加えて、ダイス1個、定数部なしの場合に最終結果のみが表示される こと(例:`(1D6) > 5`)を確認するテストケースを追加する。
AddDiceのパーサーを作成し、実行をさせる。 負の数で割れない問題 #59 等を解決する
変更された仕様
1. 目標値に四則演算を含めることができる
1d6>=1+2
等2. 割り算の表示
旧:
(2D6/2) > 2[1,3] > 2
新:
(2D6/2) > 4[1,3]/2 > 2
3. 定数の畳み込み廃止
旧:(2D6+1+3) > 4[1,3]+1+3 > 8
新:(2D6+4) > 4[1,3]+4 > 8
4. 負数の割り算ができる
1d6/-3
5. 定数部がある場合、途中式が省略されない
旧:
(1D6+1) > 5
新:
(1D6+1) > 4[4]+1 > 5
6. AddDice#rollDice以外のメソッドの廃止
AddDice::Parser
文字列をパースして、構文木を生成する。構文木の各ノードが
#eval
を持っており、それを実行することで計算結果が得られる。AddDice::ConstantFolding定数畳み込みを行う。1D6+1+2
ならば1D6+3
のようにする。検討したがやらなかったこと
NodeとEvaluetorを別ける
Evaluatorの記述が煩雑になるので、Nodeに直接eval関数を持たせた方がわかりやすい