1
+ var calculator = ( function ( ) {
2
+ exports = { } ;
3
+
4
+ var built_in_functions = {
5
+ abs : Math . abs ,
6
+ acos : Math . acos ,
7
+ asin : Math . asin ,
8
+ atan : Math . atan ,
9
+ atan2 : Math . atan2 , // 2 arg
10
+ ceil : Math . ceil ,
11
+ cos : Math . cos ,
12
+ exp : Math . exp ,
13
+ floor : Math . floor ,
14
+ log : Math . log ,
15
+ max : Math . max , // multi-arg
16
+ min : Math . min , // multi-arg
17
+ pow : Math . pow , // 2 arg
18
+ round : Math . round ,
19
+ sin : Math . sin ,
20
+ sqrt : Math . sqrt ,
21
+ } ;
22
+
23
+ var built_in_environment = {
24
+ pi : Math . PI ,
25
+ e : Math . E ,
26
+ } ;
27
+
28
+ function new_environment ( ) {
29
+ var env = { } ;
30
+ for ( var v in built_in_environment ) {
31
+ env [ v ] = built_in_environment [ v ] ;
32
+ }
33
+ return env ;
34
+ }
35
+ exports . new_environment = new_environment ;
36
+
37
+ // if first token is t, consume it and return true
38
+ function read_token ( t , tokens ) {
39
+ if ( tokens . length > 0 && tokens [ 0 ] == t ) {
40
+ tokens . shift ( ) ;
41
+ return true ;
42
+ }
43
+ return false ;
44
+ }
45
+
46
+ // builds parse tree for following BNF. Tree is either a number or
47
+ // or an array of the form [operator,tree,tree].
48
+ // <expression> ::= <term> | <expression> "+" <term> | <expression> "-" <term>
49
+ // <term> ::= <unary> | <term> "*" <unary> | <term> "/" <unary>
50
+ // <unary> ::= <factor> | "-" <factor> | "+" <factor>
51
+ // <factor> ::= <number> | "(" <expression> ")"
52
+ function parse_expression ( tokens ) {
53
+ var expression = parse_term ( tokens ) ;
54
+ while ( true ) {
55
+ if ( read_token ( '+' , tokens ) ) {
56
+ expression = [ '+' , expression , parse_term ( tokens ) ] ;
57
+ }
58
+ else if ( read_token ( '-' , tokens ) ) {
59
+ expression = [ '-' , expression , parse_term ( tokens ) ] ;
60
+ }
61
+ else break ;
62
+ }
63
+ return expression ;
64
+ }
65
+
66
+ function parse_term ( tokens ) {
67
+ var term = parse_exp ( tokens ) ;
68
+ while ( true ) {
69
+ if ( read_token ( '*' , tokens ) ) {
70
+ term = [ '*' , term , parse_exp ( tokens ) ] ;
71
+ }
72
+ else if ( read_token ( '/' , tokens ) ) {
73
+ term = [ '/' , term , parse_exp ( tokens ) ] ;
74
+ }
75
+ else break ;
76
+ }
77
+ return term ;
78
+ }
79
+
80
+ function parse_exp ( tokens ) {
81
+ var term = parse_unary ( tokens ) ;
82
+ while ( true ) {
83
+ if ( read_token ( '^' , tokens ) ) {
84
+ term = [ '^' , term , parse_unary ( tokens ) ] ;
85
+ }
86
+ else break ;
87
+ }
88
+ return term ;
89
+ }
90
+
91
+ function parse_unary ( tokens ) {
92
+ if ( read_token ( '-' , tokens ) ) {
93
+ return [ 'neg' , parse_factor ( tokens ) ] ;
94
+ }
95
+ else if ( read_token ( '+' , tokens ) ) { }
96
+ return parse_factor ( tokens ) ;
97
+ }
98
+
99
+
100
+ function parse_factor ( tokens ) {
101
+ if ( read_token ( '(' , tokens ) ) {
102
+ var exp = parse_expression ( tokens ) ;
103
+ if ( read_token ( ')' , tokens ) ) {
104
+ return exp ;
105
+ }
106
+ else throw 'Missing ) in expression' ;
107
+ }
108
+ else if ( tokens . length > 0 ) {
109
+ var token = tokens . shift ( ) ;
110
+ if ( token . search ( / [ a - z A - Z _ ] \w * / ) != - 1 ) {
111
+ // variable name
112
+ if ( read_token ( '(' , tokens ) ) {
113
+ // a function call, parse the argument(s)
114
+ var args = [ ] ;
115
+ // code assumes at least one argument
116
+ while ( true ) {
117
+ args . push ( parse_expression ( tokens ) ) ;
118
+ if ( read_token ( ',' , tokens ) ) continue ;
119
+ if ( read_token ( ')' , tokens ) ) break ;
120
+ throw "Expected comma or close paren in function call" ;
121
+ }
122
+ if ( ! ( token in built_in_functions ) ) throw "Call to unrecognized function: " + token ;
123
+ return [ 'call ' + token ] . concat ( args ) ;
124
+ }
125
+ // otherwise its just a reference to a variable
126
+ return token ;
127
+ }
128
+ // only option left: a number
129
+ var n = parseFloat ( token , 10 ) ;
130
+ if ( isNaN ( n ) ) throw 'Expected a number, got ' + String ( token ) ;
131
+ return n ;
132
+ }
133
+ else throw 'Unexpected end of expression' ;
134
+ }
135
+
136
+ function evaluate ( tree , environment ) {
137
+ if ( environment === undefined ) environment = built_in_environment ;
138
+ if ( typeof tree == 'number' ) return tree ;
139
+ else if ( typeof tree == 'string' ) return environment [ tree ] ; // might be undefined
140
+ else {
141
+ // expecting [operator,tree,...]
142
+ var args = tree . slice ( 1 ) . map ( function ( subtree ) {
143
+ return evaluate ( subtree , environment ) ;
144
+ } ) ;
145
+ if ( tree [ 0 ] . search ( / ^ c a l l / ) != - 1 ) {
146
+ // call of built-in function
147
+ var f = tree [ 0 ] . slice ( 5 ) ;
148
+ f = built_in_functions [ f ] ;
149
+ if ( f === undefined ) throw "Unknown function: " + f ;
150
+ return f . apply ( undefined , args ) ;
151
+ }
152
+ // otherwise its just an operator
153
+ else switch ( tree [ 0 ] ) {
154
+ case 'neg' :
155
+ return - args [ 0 ] ;
156
+ case '+' :
157
+ return args [ 0 ] + args [ 1 ] ;
158
+ case '-' :
159
+ return args [ 0 ] - args [ 1 ] ;
160
+ case '*' :
161
+ return args [ 0 ] * args [ 1 ] ;
162
+ case '/' :
163
+ return args [ 0 ] / args [ 1 ] ;
164
+ case '^' :
165
+ return Math . pow ( args [ 0 ] , args [ 1 ] ) ;
166
+ default :
167
+ throw 'Unrecognized operator ' + tree [ 0 ] ;
168
+ }
169
+ }
170
+ }
171
+ exports . evaluate = evaluate ;
172
+
173
+ function parse ( text ) {
174
+ // pattern matches integers, variable names, parens and the operators +, -, *, /
175
+ var pattern = / ( [ 0 - 9 ] * \. ) ? [ 0 - 9 ] + ( [ e E ] [ \- + ] ? [ 0 - 9 ] + ) ? | [ a - z A - Z _ ] \w * | \+ | \- | \* | \/ | \^ | \( | \) | \, / g;
176
+ var tokens = text . match ( pattern ) ;
177
+ return parse_expression ( tokens ) ;
178
+ }
179
+ exports . parse = parse ;
180
+
181
+ function calculate ( text , environment ) {
182
+ if ( environment === undefined ) environment = built_in_environment ;
183
+ try {
184
+ var tree = parse ( text ) ;
185
+ return evaluate ( tree , environment ) ;
186
+ }
187
+ catch ( err ) {
188
+ return err ;
189
+ }
190
+ }
191
+ exports . calculate = calculate ;
192
+
193
+ function setup_calc ( div ) {
194
+ var input = $ ( '<input></input>' , {
195
+ type : 'text' ,
196
+ size : 50
197
+ } ) ;
198
+ var output = $ ( '<div></div>' ) ;
199
+ var button = $ ( '<button>Calculate</button>' ) ;
200
+ button . bind ( "click" , function ( ) {
201
+ output . html ( String ( calculate ( input . val ( ) ) ) ) ;
202
+ } ) ;
203
+
204
+ $ ( div ) . append ( input , button , output ) ;
205
+ }
206
+ exports . setup_calc = setup_calc ;
207
+
208
+ return exports ;
209
+
210
+ } ( ) ) ;
0 commit comments