let binary = function '*' -> (*) | '+' -> (+) | _ -> (-)
let appendOp ((ll, lrs), rs) v = function
| '*' as op -> (v, (binary op, ll)::lrs), rs
| op -> (v, []), (binary op, (ll, lrs))::rs
let evalOp eval (x, xs) = List.foldBack (fun ((@), l) r -> eval l @ r) xs (eval x)
let eval = evalOp <| evalOp id
let literal x = (x, []), []
let replaceHead v ((_, lrs), rs) = (v, lrs), rs
let step (expr, view, op) = function
| '=' -> let view = eval expr in literal view, view, Some '='
| c when '0' <= c && c <= '9' ->
let d = int c - int '0'
match op with
| Some '=' -> literal d, d, None
| Some op -> appendOp expr d op, d, None
| None ->
let view = view * 10 + d
replaceHead view expr, view, None
| op -> expr, (eval expr), Some op
let calc = Seq.fold step (literal 0, 0, None) >> fun (_,v,_) -> string v
let act = calc src
let msg
= if act
= exp then
"ok" else "error" printfn
"%s { src = %A, exp = %A, act = %A }" msg src
exp act
// お題:電卓を実装する
// 引数はボタン入力履歴で、戻り値は現在の表示
// ボタンは 0123456789-+*= のみ
"5" => "5"
"5+" => "5"
"5+3" => "3"
"5+3=" => "8"
"5*-3=" => "2" // 最後に入力した演算子が有効
"5-3+" => "2" // 途中結果を表示する
"5-3*2=" => "-1" // -+ よりも * が優先される
// 初期値は 0
"" => "0"
"-10=" => "-10"
"*10=" => "0"
"4+3=2=" => "2" // = の次に数値が入力された場合、途中結果は捨てる
"4+3=*2=" => "14" // * よりも = が優先される
bGV0IGJpbmFyeSA9IGZ1bmN0aW9uICcqJyAtPiAoKikgfCAnKycgLT4gKCspIHwgXyAtPiAoLSkKbGV0IGFwcGVuZE9wICgobGwsIGxycyksIHJzKSB2ID0gZnVuY3Rpb24KICAgIHwgJyonIGFzIG9wIC0+ICh2LCAoYmluYXJ5IG9wLCBsbCk6OmxycyksIHJzCiAgICB8IG9wIC0+ICh2LCBbXSksIChiaW5hcnkgb3AsIChsbCwgbHJzKSk6OnJzCgpsZXQgZXZhbE9wIGV2YWwgKHgsIHhzKSA9IExpc3QuZm9sZEJhY2sgKGZ1biAoKEApLCBsKSByIC0+IGV2YWwgbCBAIHIpIHhzIChldmFsIHgpCmxldCBldmFsID0gZXZhbE9wIDx8IGV2YWxPcCBpZApsZXQgbGl0ZXJhbCB4ID0gKHgsIFtdKSwgW10KbGV0IHJlcGxhY2VIZWFkIHYgKChfLCBscnMpLCBycykgPSAodiwgbHJzKSwgcnMKCmxldCBzdGVwIChleHByLCB2aWV3LCBvcCkgPSBmdW5jdGlvbgogICAgfCAnPScgLT4gIGxldCB2aWV3ID0gZXZhbCBleHByIGluIGxpdGVyYWwgdmlldywgdmlldywgU29tZSAnPScKICAgIHwgYyB3aGVuICcwJyA8PSBjICYmIGMgPD0gJzknIC0+CiAgICAgICAgbGV0IGQgPSBpbnQgYyAtIGludCAnMCcKICAgICAgICBtYXRjaCBvcCB3aXRoCiAgICAgICAgfCBTb21lICc9JyAtPiBsaXRlcmFsIGQsIGQsIE5vbmUKICAgICAgICB8IFNvbWUgb3AgLT4gYXBwZW5kT3AgZXhwciBkIG9wLCBkLCBOb25lCiAgICAgICAgfCBOb25lIC0+CiAgICAgICAgICAgIGxldCB2aWV3ID0gdmlldyAqIDEwICsgZAogICAgICAgICAgICByZXBsYWNlSGVhZCB2aWV3IGV4cHIsIHZpZXcsIE5vbmUKCiAgICB8IG9wIC0+IGV4cHIsIChldmFsIGV4cHIpLCBTb21lIG9wCgpsZXQgY2FsYyA9IFNlcS5mb2xkIHN0ZXAgKGxpdGVyYWwgMCwgMCwgTm9uZSkgPj4gZnVuIChfLHYsXykgLT4gc3RyaW5nIHYKCmxldCAoPT4pIHNyYyBleHAgPSAKICAgIGxldCBhY3QgPSBjYWxjIHNyYwogICAgbGV0IG1zZyA9IGlmIGFjdCA9IGV4cCB0aGVuICJvayIgZWxzZSAiZXJyb3IiIAogICAgcHJpbnRmbiAiJXMgeyBzcmMgPSAlQSwgZXhwID0gJUEsIGFjdCA9ICVBIH0iIG1zZyBzcmMgZXhwIGFjdAoKLy8g44GK6aGM77ya6Zu75Y2T44KS5a6f6KOF44GZ44KLCi8vIOW8leaVsOOBr+ODnOOCv+ODs+WFpeWKm+WxpeattOOBp+OAgeaIu+OCiuWApOOBr+ePvuWcqOOBruihqOekugovLyDjg5zjgr/jg7Pjga8gMDEyMzQ1Njc4OS0rKj0g44Gu44G/CiI1IiA9PiAiNSIKIjUrIiA9PiAiNSIKIjUrMyIgPT4gIjMiCiI1KzM9IiA9PiAiOCIKCiI1Ki0zPSIgPT4gIjIiIC8vIOacgOW+jOOBq+WFpeWKm+OBl+OBn+a8lOeul+WtkOOBjOacieWKuQoiNS0zKyIgPT4gIjIiIC8vIOmAlOS4ree1kOaenOOCkuihqOekuuOBmeOCiwoiNS0zKjI9IiA9PiAiLTEiIC8vIC0rIOOCiOOCiuOCgiAqIOOBjOWEquWFiOOBleOCjOOCiwoKLy8g5Yid5pyf5YCk44GvIDAKIiIgPT4gIjAiCiItMTA9IiA9PiAiLTEwIgoiKjEwPSIgPT4gIjAiCgoiNCszPTI9IiA9PiAiMiIgLy8gPSDjga7mrKHjgavmlbDlgKTjgYzlhaXlipvjgZXjgozjgZ/loLTlkIjjgIHpgJTkuK3ntZDmnpzjga/mjajjgabjgosKIjQrMz0qMj0iID0+ICIxNCIgLy8gKiDjgojjgorjgoIgPSDjgYzlhKrlhYjjgZXjgozjgos=
ok { src = "5", exp = "5", act = "5" }
ok { src = "5+", exp = "5", act = "5" }
ok { src = "5+3", exp = "3", act = "3" }
ok { src = "5+3=", exp = "8", act = "8" }
ok { src = "5*-3=", exp = "2", act = "2" }
ok { src = "5-3+", exp = "2", act = "2" }
ok { src = "5-3*2=", exp = "-1", act = "-1" }
ok { src = "", exp = "0", act = "0" }
ok { src = "-10=", exp = "-10", act = "-10" }
ok { src = "*10=", exp = "0", act = "0" }
ok { src = "4+3=2=", exp = "2", act = "2" }
ok { src = "4+3=*2=", exp = "14", act = "14" }