-
Notifications
You must be signed in to change notification settings - Fork 0
/
Calculator.fs
146 lines (125 loc) · 4.79 KB
/
Calculator.fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
namespace MyFullStack01
open WebSharper
[<JavaScript>]
module Calculator =
module Ast =
type var = string
type Expr =
| Number of float
| BinOp of (float -> float -> float) * Expr * Expr
| Var of var
| FunCall of var * Expr
static member Sum (e1, e2) = BinOp (( + ), e1, e2)
static member Diff (e1, e2) = BinOp (( - ), e1, e2)
static member Prod (e1, e2) = BinOp (( * ), e1, e2)
static member Ratio (e1, e2) = BinOp (( / ), e1, e2)
module Language =
open System
open WebSharper.JavaScript
let private matchToken pattern (s: string) : (string * string) option =
let regexp = new RegExp("^(" + pattern + ")(.*)")
if regexp.Test s then
let results = regexp.Exec s
if results ==. null then
None
else
Some results
|> Option.map (fun x -> (x.[1], x.[2]))
else
None
// eta reduction
let (|WHITESPACE|_|) s = matchToken @"[ |\t|\n|\n\r]+" s
let rec MatchTokenNoWS s pattern =
match (|WHITESPACE|_|) s with
| Some (_, rest) ->
rest |> matchToken pattern
| None ->
s |> matchToken pattern
let MatchToken s f pattern =
pattern |> MatchTokenNoWS s |> Option.bind f
let MatchSymbol s pattern =
pattern |> MatchToken s (fun (_, rest) -> rest |> Some)
let rec (|Star|_|) f acc s =
match f s with
| Some (res, rest) ->
(|Star|_|) f (res :: acc) rest
| None ->
(acc |> List.rev , s) |> Some
let (|NUMBER|_|) s =
@"[0-9]+\.?[0-9]*" |> MatchToken s
(fun (n, rest) -> (n |> Double.Parse, rest) |> Some)
let (|ID|_|) s =
"[a-zA-Z]+" |> MatchToken s (fun res -> res |> Some)
let (|PLUS|_|) s = @"\+" |> MatchSymbol s
let (|MINUS|_|) s = @"\-" |> MatchSymbol s
let (|MUL|_|) s = @"\*" |> MatchSymbol s
let (|DIV|_|) s = "/" |> MatchSymbol s
let (|LPAREN|_|) s = @"\(" |> MatchSymbol s
let (|RPAREN|_|) s = @"\)" |> MatchSymbol s
// Non-terminal symbols
// Productions
let rec (|Factor|_|) = function
| NUMBER (n, rest) ->
(Ast.Expr.Number n, rest) |> Some
| ID (v, rest) ->
match rest with
| LPAREN (Expression (arg, RPAREN rest)) ->
(Ast.Expr.FunCall (v, arg), rest) |> Some
| _ ->
(Ast.Expr.Var v, rest) |> Some
| LPAREN (Expression (e, RPAREN rest)) ->
(e, rest) |> Some
| _ ->
None
and (|Term|_|) = function
| Factor (e1, rest) ->
match rest with
| MUL (Term (e2, rest)) ->
(Ast.Expr.Prod (e1, e2), rest) |> Some
| DIV (Term (e2, rest)) ->
(Ast.Expr.Ratio (e1, e2), rest) |> Some
| _ ->
(e1, rest) |> Some
| _ ->
None
and (|Sum|_|) = function
| Term (e1, rest) ->
match rest with
| PLUS (Sum (e2, rest)) ->
(Ast.Expr.Sum (e1, e2), rest) |> Some
| MINUS (Sum (e2, rest)) ->
(Ast.Expr.Diff (e1, e2), rest) |> Some
| _ ->
(e1, rest) |> Some
| _ ->
None
and (|Expression|_|) = (|Sum|_|)
let (|Eof|_|) s =
if String.IsNullOrEmpty s then
() |> Some
else
match s with
| WHITESPACE (_, rest) when rest |> String.IsNullOrEmpty ->
() |> Some
| _ ->
None
module Evaluator =
open Ast
let rec Eval (env: (string * float) list) e =
match e with
| Expr.Number num -> num
| Expr.BinOp (f, e1, e2) -> f (Eval env e1) (Eval env e2)
| Expr.Var v ->
env
|> List.tryFind (fun (_v, _) -> _v = v)
|> function
| None ->
"Unbound variable: " + v |> failwith
| Some (_, value) ->
value
| Expr.FunCall (f, e) when f.ToLower() = "sin" ->
Eval env e |> sin
| Expr.FunCall (f, e) when f.ToLower() = "cos" ->
Eval env e |> cos
| Expr.FunCall (f, _) ->
"Unknown function: " + f |> failwith