eSEATによる計算機の実装例
Simple Wiki Based Contents Management System
ソフトウェア関連 >> RTコンポーネント関連 >> eSEAT2 >> eSEATによる計算機の実装例

eSEATによる計算機の実装例(その2)

eSEATのGUI作成機能を使って、簡単な計算機ができることをやってみましたが、今度は、GUIパネル(calc_ui)と計算部分(calc_core)を分けて2つのRTCをつくることで、電卓のような計算機を作ってみます。
計算のコアの部分となるRTCもeSEATで、状態遷移モデルを使って実装してみましょう。

GUIパネルをつくる


GUIパネル部分は、簡単な計算機の実装で行ったものとほぼ同じですが、今度は、計算部分のRTCとのデータ授受のためにデータポートを設定します。
<?xml version="1.0" encoding="UTF-8"?>
<seatml>
 <general name="calc">
   <adaptor name="out" type="rtcout" datatype="TimedString" />
   <adaptor name="in" type="rtcin" datatype="TimedString" />
 </general>

</seatml>
あとは、ボタン、ラベル等使ってGUIアイテムを配置します。また、各ボタンに対する動作は、数字や演算を表す文字列をデータポートへ出力し、入力ポートから来た文字列を一行入力アイテムに表示するという単純なものとします。
<?xml version="1.0" encoding="UTF-8"?>
<seatml>
 <general name="calc">
   <adaptor name="out" type="rtcout" datatype="TimedString" />
   <adaptor name="in" type="rtcin" datatype="TimedString" />
 </general>

 <state name="main_mode">
   <label text="SimpleCalc" bg_color="#ff0000" color="#ffffff" colspan="4" />
	     
   <brk />
   <input id="disp" width="50" colspan="4">0</input>

   <brk />
   <button label="C" > <message sendto="out">Clear</message> </button>
   <space length="2" />
   <button label="+" > <message sendto="out">Add</message> </button>

   <brk />
   <button label="1" > <message sendto="out">1</message> </button>
   <button label="2" > <message sendto="out">2</message> </button>
   <button label="3" > <message sendto="out">3</message> </button>
   <button label="-" > <message sendto="out">Subtract</message> </button>

   <brk />
   <button label="4" > <message sendto="out">4</message> </button>
   <button label="5" > <message sendto="out">5</message> </button>
   <button label="6" > <message sendto="out">6</message> </button>
   <button label="*" > <message sendto="out">Multiply</message> </button>

   <brk />
   <button label="7" > <message sendto="out">7</message> </button>
   <button label="8" > <message sendto="out">8</message> </button>
   <button label="9" > <message sendto="out">9</message> </button>
   <button label="/" > <message sendto="out">Divide</message> </button>

   <brk />
   <space length="1" />
   <button label="0" > <message sendto="out">0</message> </button>
   <button label="." > <message sendto="out">Comma</message> </button>
   <button label="=" > <message sendto="out">Equal</message> </button>

   <rule source="in" >
     <script>
seat.setEntry("main_mode:disp", rtc_in_data.data)
     </script>
   </rule>
 </state>

</seatml>
2014/07/24 のアップデートで、seatml内のlabel, button, input, textの要素の指定は、「stateの識別子+":"+要素の識別子」に仕様変更しました
これでGUIパネルのRTCの完成です。

計算機のコアロジックのRTCを作る

次に計算機のコアロジックとなるRTCを設計していきましょう。今回は、通常の電卓のような振舞にしたいということですので、電卓を手に取ってその動作を検証しながらどういうアルゴリズムにすればいいか考えていけばよいと思います。
計算機の場合には、コアロジックをどう作るかはいろいろ考えることができます。
実際に電卓をいろいろ操作していくと、数字のボタンを押せばその数字が1の位に表示されていきます。また、小数点を押したかどうかでも表示される数字が異なる(小数点は数字の入力では一回しか押せませんよね)ということもわかります。
また、”=”のボタンも電源をつけた状態と計算を一度した後では振る舞いが違うこともわかります。”C”ボタンの場合には、2度以上押せば観戦に初期状態に戻りますが、状態のよっては現在の入力のみをキャンセルするという動作もしています。
これはすなわち押されるキーに対する動作が電卓の状態によって変わっているということが理解できると思います。このような同じキー操作(ここでは同じデータの入力)があっても、その時の内部状態によって、実行する動作が変わる場合には、有限オートマトンを使うと結構きれいに書くことができます。
eSEATは、有限オートマトンを記述することができますので、IF文などは極力使わずにさまざまな内部状態を定義して、計算機のコアロジックを作成していきましょう。

数字の入力

最初に計算に必要な数字の入力について考えてみます。数字の入力には、0~9のキーと"."(コンマ)のキーで入力します。このとき、初期状態では、1から9までのキーか"."が入力されると次桁の数字を入力可能で、0だとそのままの状態を維持します。また、"."が押された後だと小数点以下の数字には0~9のみで2度と"."の入力はできません。
このことを考えると、初期状態(またはリセット状態)、整数の入力の状態、実数の入力の状態に分けることができます。初期状態、整数の入力の状態、実数の入力の状態をそれぞれ、reset_mode, int1_mode, float1_modeとすると状態遷移図は、下図のように書くことができます。

これで最初の数字入力が終了です。

演算子の入力

次に電卓で行われるキー操作は演算子の入力です。数字の入力終了は、上記のどの状態からも演算子のキーを押すことができます。演算子を入力した場合には、現在の数字と演算子を記憶しておき、新らしい数字の入力待ちの状態になるということです。
数字の入力ですので、reset状態に戻っても良いような気がしますが、完全にリセット状態と2番目の数字の入力状態は少し異なります。初期状態では、記憶している数字、演算子とも記憶していない状態であり、2番目の数字の入力状態は、前の数字(計算結果かもしれませんが)と現在の演算子を記憶しているのはもちろんですが、演算子の再入力も受け入れなければいけません。そのため新たな状態として定義しておきましょう。この2番目の数字の入力状態をinput2_modeとしておきます。数字の入力自体は、前日のように実数の入力になるか整数のみの入力になるかで状態を分けていましたので、ここでも整数の入力状態、実数の入力状態として、int2_modeとfloat2_modeを定義しましょう。
ここまでの内部状態定義と状態遷移を図にすると下図のようになります。

電卓の計算の実行

上記までで2つの数字の入力ができるようになりました。あとは計算のみですが、実際の計算は、演算子を入力または"="の入力によって実行されていることがわかります。また、"="で一度計算を実行した後に、再度"="を入力すると繰り返し計算を実行してくれます。
このように繰り替え計算をする状態も分けて考えた方がよさそうなので、calc_modeという状態を作ってみたいと思います。この状態は、2つの数字が入力された後の計算(演算子の入力)とすれば、上記の状態遷移図に演算子の入力の場合と"="を入力した場合を入れていけば状態遷移図が完成します。

内部状態遷移図

上記までの考え方に従って計算のコアとなるRTCの内部状態遷移図は下記のように書くことができます。

計算のためのスクリプトの実装

上記までで状態遷移図ができましたので、これに従ってSEATMLスクリプトを記述していきます。また、各入力時に行う動作(数字の生成と計算)についてもscript要素を使って記述していきましょう。
<?xml version="1.0" encoding="UTF-8"?>
<seatml>
 <general name="calc">
   <adaptor name="out" type="rtcout" datatype="TimedString" />
   <adaptor name="in" type="rtcin" datatype="TimedString" />
   <script>
global opr, ans, lastkey, current
opr="="
ans=None
current="0"
   </script>
   <script>
def calc_exec():
  global current, ans
  print ans, opr, current
  try:
    exp = "float("+ans +")"+ opr + current
    print exp
    ans = str(eval(exp))
    return ans
  except:
    return "ERROR"

def getOperation(str):
  if str == 'Add':
    return '+'
  elif str == 'Subtract':
    return '-'
  elif str == 'Multiply':
    return '*'
  elif str == 'Divide':
    return '/'
  else:
   return None
   </script>
 </general>

 <!-- Reset (Init) Mode : Input first value -->
 <state name="reset_mode">
   <onentry>
      <script sendto="out">
print "=== Reset Mode ===="
current="0"
ans=current
opr=None
rtc_result=current
      </script>
   </onentry>   
   <rule>
      <key>0</key>
      <script sendto="out">
current=rtc_in_data
rtc_result=current
      </script>
   </rule>   
   <rule>
      <key>(1|2|3|4|5|6|7|8|9)</key>
      <script sendto="out">
current=rtc_in_data
rtc_result=current
      </script>
      <statetransition>int1_mode</statetransition>
   </rule>
   <rule>
     <key>Comma</key>
     <script sendto="out">
current="0."
rtc_result=current
     </script>
     <statetransition>float1_mode</statetransition>
    </rule>
    <rule>
      <key>Clear</key>
      <statetransition>reset_mode</statetransition>
    </rule>
    <rule>
      <key>(Add|Subtract|Multiply|Divide)</key>
      <script sendto="out">
ans=current
opr=getOperation(rtc_in_data)
      </script>
      <statetransition>input2_mode</statetransition>
   </rule>   
 </state>

 <state name="int1_mode">
   <onentry>
     <script >
print "=== int1 Mode ===="
     </script>
   </onentry>
   <rule>
      <key>(0|1|2|3|4|5|6|7|8|9)</key>
      <script sendto="out">
current=current+rtc_in_data
rtc_result=current
      </script>
   </rule>
   <rule>
     <key>Comma</key>
     <script sendto="out">
current=current+"."
rtc_result=current
     </script>
     <statetransition>float1_mode</statetransition>
   </rule>
   <rule>
      <key>(Add|Subtract|Multiply|Divide)</key>
      <script sendto="out">
ans=current
opr=getOperation(rtc_in_data)
      </script>
      <statetransition>input2_mode</statetransition>
   </rule>
   <rule>
      <key>Clear</key>
      <statetransition>reset_mode</statetransition>
   </rule>
 </state>

 <state name="float1_mode">
   <onentry>
     <script >
print "=== float1 Mode ===="
     </script>
   </onentry>
   <rule>
      <key>(0|1|2|3|4|5|6|7|8|9)</key>
      <script sendto="out">
current=current+rtc_in_data
rtc_result=current
      </script>
   </rule>
   <rule>
      <key>(Add|Subtract|Multiply|Divide)</key>
      <script sendto="out">
ans=current
opr=getOperation(rtc_in_data)
      </script>
      <statetransition>input2_mode</statetransition>
   </rule>
   <rule>
      <key>Clear</key>
      <statetransition>reset_mode</statetransition>
   </rule>
 </state>

 <!-- Input2 Mode: Input second value -->
 <state name="input2_mode">
   <onentry>
     <script >
print "=== input2 Mode ===="
     </script>
   </onentry>
   <rule>
      <key>Clear</key>
      <statetransition>reset_mode</statetransition>
   </rule>   
   <rule>
      <key>0</key>
      <script sendto="out">
current=rtc_in_data
rtc_result=current
      </script>
   </rule>   
   <rule>
      <key>(1|2|3|4|5|6|7|8|9)</key>
      <script sendto="out">
current=rtc_in_data
rtc_result=current
      </script>
      <statetransition>int2_mode</statetransition>
   </rule>
   <rule>
     <key>Comma</key>
     <script sendto="out">
current="0."
rtc_result=current
     </script>
     <statetransition>float2_mode</statetransition>
   </rule>
   <rule>
      <key>Clear</key>
      <statetransition>reset_mode</statetransition>
   </rule>
   <rule>
      <key>(Add|Subtract|Multiply|Divide)</key>
      <script sendto="out">opr=getOperation(rtc_in_data)</script>
   </rule>
 </state>

 <state name="int2_mode">
   <onentry>
     <script >
print "=== int2 Mode ===="
     </script>
   </onentry>
   <rule>
      <key>(0|1|2|3|4|5|6|7|8|9)</key>
      <script sendto="out">
current=current+rtc_in_data
rtc_result=current
      </script>
   </rule>
   <rule>
     <key>Comma</key>
     <script sendto="out">
current=current+"."
rtc_result=current
     </script>
     <statetransition>float2_mode</statetransition>
   </rule>
   <rule>
      <key>(Add|Subtract|Multiply|Divide)</key>
      <script sendto="out">
ans=current
opr=getOperation(rtc_in_data)
rtc_result=calc_exec()
      </script>
      <statetransition>calc_mode</statetransition>
   </rule>
   <rule>
      <key>Clear</key>
      <statetransition>input2_mode</statetransition>
   </rule>
   <rule>
     <key>Equal</key>
     <script sendto="out">
ans=calc_exec()
rtc_result=ans
     </script>
      <statetransition>calc_mode</statetransition>
   </rule>
 </state>

 <state name="float2_mode">
   <onentry>
     <script >
print "=== float2 Mode ===="
     </script>
   </onentry>
   <rule>
      <key>(0|1|2|3|4|5|6|7|8|9)</key>
      <script sendto="out">
current=current+rtc_in_data
rtc_result=current
      </script>
   </rule>
   <rule>
      <key>(Add|Subtract|Multiply|Divide)</key>
      <script sendto="out">
ans=current
opr=getOperation(rtc_in_data)
rtc_result=calc_exec()
      </script>
      <statetransition>calc_mode</statetransition>
   </rule>
   <rule>
      <key>Clear</key>
      <statetransition>input2_mode</statetransition>
   </rule>
   <rule>
     <key>Equal</key>
     <script sendto="out">
ans=calc_exec()
rtc_result=ans
     </script>
      <statetransition>calc_mode</statetransition>
   </rule>
 </state>

 <!-- Calc Mode: Apply calcuration -->
 <state name="calc_mode">
   <onentry>
     <script >
print "=== calc Mode ===="
     </script>
   </onentry>
   <rule>
      <key>Clear</key>
      <statetransition>reset_mode</statetransition>
   </rule>
   <rule>
      <key>(1|2|3|4|5|6|7|8|9)</key>
      <script sendto="out">
current=rtc_in_data
rtc_result=current
      </script>
      <statetransition>int2_mode</statetransition>
   </rule>
   <rule>
     <key>Comma</key>
     <script sendto="out">
current="0."
rtc_result=current
     </script>
     <statetransition>float2_mode</statetransition>
   </rule>
   <rule>
      <key>(Add|Subtract|Multiply|Divide)</key>
      <script sendto="out">
opr=getOperation(rtc_in_data)
      </script>
      <statetransition>input2_mode</statetransition>
   </rule>
   <rule>
      <key>Equal</key>
      <script sendto="out">
ans=calc_exec()
rtc_result=ans
      </script>
   </rule>
 </state>

</seatml>

計算機RTシステムの実行

出来上がった2つのRTCを起動して、下図のように接続して実行してみてください。

これで電卓の完成です。このようにeSEATでは、GUIのみのRTCと有限オートマトンのRTCを作成することができます。他に電卓として逆ポーランド型のものもありますので、その場合にはコアロジックのRTCを置き換えることで簡単に電卓の機能を変更することができます。

資料