Skip to content
This repository
Browse code

Add calculator

Test Plan: Tested outside devappserver by typing in some random expressions

Reviewers: alpert

Reviewed By: alpert

Differential Revision: http://phabricator.khanacademy.org/D756
  • Loading branch information...
commit 8ba5ce5566b9e5c3122a4ea4597cf1461ea9fc44 1 parent 064281e
Ben Eater authored
4  Makefile
... ...
@@ -0,0 +1,4 @@
  1
+utils/calculator.js: build/calculator/calculator.jison build/calculator/calculator-tail.js
  2
+	jison -m js build/calculator/calculator.jison -o utils/Calculator.js
  3
+	cat build/calculator/calculator-tail.js >>utils/Calculator.js
  4
+	mv utils/Calculator.js utils/calculator.js
99  build/calculator/calculator-tail.js
... ...
@@ -0,0 +1,99 @@
  1
+var Calculator = (function(parser) {
  2
+    var CalculatorError = function(message) {
  3
+        this.message = message;
  4
+    };
  5
+    CalculatorError.prototype = new Error();
  6
+    CalculatorError.prototype.constructor = CalculatorError;
  7
+
  8
+    parser.parseError = function parseError(str, hash) {
  9
+        throw new CalculatorError("err");
  10
+    };
  11
+
  12
+    return _.bindAll({
  13
+        angleMode: "DEG",
  14
+        parser: parser,
  15
+        parse: _.bind(parser.parse, parser),
  16
+
  17
+        evaluate: function(tree, ans) {
  18
+            var toRad = function(ang) {
  19
+                if (Calculator.angleMode === "DEG") {
  20
+                    return ang * Math.PI / 180;
  21
+                }
  22
+                return ang;
  23
+            };
  24
+            var fromRad = function(ang) {
  25
+                if (Calculator.angleMode === "DEG") {
  26
+                    return ang / Math.PI * 180;
  27
+                }
  28
+                return ang;
  29
+            };
  30
+            if (tree === "ans") {
  31
+                if (ans != null) {
  32
+                    return ans;
  33
+                } else {
  34
+                    throw new CalculatorError("Invalid variable ans");
  35
+                }
  36
+            } else if (_.isNumber(tree)) {
  37
+                return tree;
  38
+            } else if (_.isArray(tree)) {
  39
+                var fns = {
  40
+                    "+": function(a, b) { return a + b; },
  41
+                    "-": function(a, b) { return b == null ? -a : a - b; },
  42
+                    "*": function(a, b) { return a * b; },
  43
+                    "/": function(a, b) { return a / b; },
  44
+                    "^": function(a, b) { return Math.pow(a, b); },
  45
+                    "!": function f(a) { return a <= 1 ? 1 : a * f(a - 1); },
  46
+                    sqrt: function(a) { return Math.pow(a, 0.5); },
  47
+                    sin: function(a) { return Math.sin(toRad(a)); },
  48
+                    cos: function(a) { return Math.cos(toRad(a)); },
  49
+                    tan: function(a) {
  50
+                        var ans = Math.tan(toRad(a));
  51
+                        if (isNaN(ans) || Math.abs(ans) > Math.pow(2, 53)) {
  52
+                            throw new CalculatorError("undefined");
  53
+                        }
  54
+                        return ans;
  55
+                    },
  56
+                    asin: function(a) {
  57
+                        var ans = fromRad(Math.asin(a));
  58
+                        if (isNaN(ans)) {
  59
+                            throw new CalculatorError("undefined");
  60
+                        }
  61
+                        return ans;
  62
+                    },
  63
+                    acos: function(a) {
  64
+                        var ans = fromRad(Math.acos(a));
  65
+                        if (isNaN(ans)) {
  66
+                            throw new CalculatorError("undefined");
  67
+                        }
  68
+                        return ans;
  69
+                    },
  70
+                    atan: function(a) {
  71
+                        var ans = fromRad(Math.atan(a));
  72
+                        if (isNaN(ans)) {
  73
+                            throw new CalculatorError("undefined");
  74
+                        }
  75
+                        return ans;
  76
+                    }
  77
+                };
  78
+
  79
+                if (tree[0] in fns) {
  80
+                    var self = this;
  81
+                    return fns[tree[0]].apply(
  82
+                        this, _.map(tree.slice(1), function(t) {
  83
+                            return self.evaluate(t, ans); }));
  84
+                } else {
  85
+                    throw new CalculatorError("err");
  86
+                }
  87
+            } else {
  88
+                throw new CalculatorError(
  89
+                    "Invalid type " + Object.prototype.toString.call(tree));
  90
+            }
  91
+        },
  92
+
  93
+        calculate: function(str, ans) {
  94
+            var tree = this.parse(str);
  95
+            return this.evaluate(tree, ans);
  96
+        },
  97
+        CalculatorError: CalculatorError
  98
+    }, "evaluate", "calculate");
  99
+})(Calculator);
65  build/calculator/calculator.jison
... ...
@@ -0,0 +1,65 @@
  1
+
  2
+/* description: Parses end executes mathematical expressions. */
  3
+
  4
+/* lexical grammar */
  5
+%lex
  6
+%%
  7
+
  8
+\s+                   /* skip whitespace */
  9
+([\.0-9]+)\b         return 'NUM'
  10
+"+"                   return '+'
  11
+"-"                   return '-'
  12
+"*"                   return '*'
  13
+"/"                   return '/'
  14
+"^"                   return '^'
  15
+"!"                   return '!'
  16
+"("                   return '('
  17
+")"                   return ')'
  18
+"ans"                 return 'ANS'
  19
+[a-z]+                return 'FN'
  20
+<<EOF>>               return 'EOF'
  21
+.                     return 'INVALID'
  22
+
  23
+/lex
  24
+
  25
+/* operator associations and precedence */
  26
+
  27
+%right UMINUS
  28
+%left '+' '-'
  29
+%left '*' '/'
  30
+%left '^'
  31
+%right '!'
  32
+
  33
+%start expressions
  34
+
  35
+%% /* language grammar */
  36
+
  37
+expressions
  38
+    : e EOF
  39
+        {return $1;}
  40
+    ;
  41
+
  42
+e
  43
+    : e '+' e
  44
+        {$$ = ["+", $1, $3];}
  45
+    | e '-' e
  46
+        {$$ = ["-", $1, $3];}
  47
+    | e '*' e
  48
+        {$$ = ["*", $1, $3];}
  49
+    | e '/' e
  50
+        {$$ = ["/", $1, $3];}
  51
+    | e '^' e
  52
+        {$$ = ["^", $1, $3];}
  53
+    | e '!'
  54
+        {$$ = ["!", $1];}
  55
+    | '-' e %prec UMINUS
  56
+        {$$ = ["-", $2];}
  57
+    | FN '(' e ')'
  58
+        {$$ = [$1, $3];}
  59
+    | '(' e ')'
  60
+        {$$ = $2;}
  61
+    | ANS
  62
+        {$$ = $1;}
  63
+    | NUM
  64
+        {$$ = Number(yytext);}
  65
+    ;
67  css/khan-exercise.css
@@ -121,6 +121,73 @@ body.debug .graphie { outline: 1px dashed #fdd; }
121 121
   color: #fff;
122 122
   text-decoration: none;
123 123
 }
  124
+
  125
+#answer_area .calculator .history {
  126
+    font: 14px/1.5 Menlo, Monaco, Courier, monospace;
  127
+    margin: 0 0 10px;
  128
+    width: 183px;
  129
+    max-height: 150px;
  130
+    overflow-y: scroll;
  131
+    overflow-x: hidden;
  132
+}
  133
+
  134
+#answer_area .calculator .history .output {
  135
+    text-align: right;
  136
+}
  137
+
  138
+#answer_area .calculator .history input {
  139
+    box-sizing: border-box;
  140
+    display: block;
  141
+    font: inherit;
  142
+    margin: 5px 3px 3px;
  143
+    width: 169px;
  144
+    padding-right: 25px;
  145
+}
  146
+
  147
+#answer_area .calculator .history .input {
  148
+    position: relative;
  149
+}
  150
+
  151
+#answer_area .calculator .history .status a {
  152
+    position: absolute;
  153
+    font: 10px/1.5 Menlo, Monaco, Courier, monospace;
  154
+    color: #999;
  155
+    text-decoration: none;
  156
+    line-height: 10px;
  157
+    height: 19px;
  158
+    top: 5px;
  159
+    right: 6px;
  160
+}
  161
+
  162
+#answer_area .calculator .keypad .row {
  163
+    margin: 5px 0;
  164
+}
  165
+
  166
+#answer_area .calculator .keypad .row a {
  167
+    background: #ccc;
  168
+    border-radius: 2px;
  169
+    color: #000;
  170
+    display: inline-block;
  171
+    margin: 0 5px 0 0;
  172
+    text-align: center;
  173
+    text-decoration: none;
  174
+    width: 31px;
  175
+}
  176
+
  177
+#answer_area .calculator .keypad .row a.dark {
  178
+    background: #aaa;
  179
+}
  180
+
  181
+#answer_area .calculator .keypad a sup {
  182
+    vertical-align: super;
  183
+    font-size: 85%;
  184
+    line-height: 0;
  185
+}
  186
+
  187
+#answer_area .calculator .keypad a.wide {
  188
+    width: 67px;
  189
+}
  190
+
124 191
 #solutionarea {
125 192
     min-height: 35px;
126 193
     padding: 10px;
28  exercises/khan-exercise.html
@@ -56,6 +56,34 @@
56 56
                 </div>
57 57
             </div>
58 58
         </form>
  59
+        <div id="calculator" class="info-box">
  60
+            <span class="info-box-header">Calculator</span>
  61
+            <div class="calculator">
  62
+                <div class="history fancy-scrollbar">
  63
+                    <div class="row input"><input type="text"><div class="status"><a href="#" data-behavior="angle-mode"><br>DEG</a></div></div>
  64
+                </div>
  65
+                <div class="keypad">
  66
+                    <div class="row">
  67
+                    <a href="#" data-text="asin(">sin<sup>-1</sup></a><a href="#" data-text="acos(">cos<sup>-1</sup></a><a href="#" data-text="atan(">tan<sup>-1</sup></a><a href="#" data-behavior="bs">del</a><a href="#" data-behavior="clear" class="dark">ac</a>
  68
+                    </div>
  69
+                    <div class="row">
  70
+                    <a href="#" data-text="sin(">sin</a><a href="#" data-text="cos(">cos</a><a href="#" data-text="tan(">tan</a><a href="#" data-text="sqrt(">&radic;</a><a href="#" data-text="^">x<sup>y</sup></a>
  71
+                    </div>
  72
+                    <div class="row">
  73
+                    <a href="#" class="dark">7</a><a href="#" class="dark">8</a><a href="#" class="dark">9</a><a href="#">(</a><a href="#">)</a>
  74
+                    </div>
  75
+                    <div class="row">
  76
+                    <a href="#" class="dark">4</a><a href="#" class="dark">5</a><a href="#" class="dark">6</a><a href="#" data-text="*">&times;</a><a href="#" data-text="/">&divide;</a>
  77
+                    </div>
  78
+                    <div class="row">
  79
+                    <a href="#" class="dark">1</a><a href="#" class="dark">2</a><a href="#" class="dark">3</a><a href="#">+</a><a href="#" data-text="-">&minus;</a>
  80
+                    </div>
  81
+                    <div class="row">
  82
+                    <a href="#" class="dark">0</a><a href="#" class="dark">.</a><a href="#" data-text="ans">ans</a><a href="#" data-behavior="evaluate" class="wide">=</a>
  83
+                    </div>
  84
+                </div>
  85
+            </div>
  86
+        </div>
59 87
         <div id="issue" class="info-box" style="display:none;">
60 88
             <span class="info-box-header">Report a Problem</span>
61 89
             <span id="issue-status" class="info-box-sub-description"></span>
96  khan-exercise.js
@@ -732,7 +732,7 @@ var Khan = (function() {
732 732
         }
733 733
 
734 734
         // Base modules required for every problem
735  
-        Khan.require(["answer-types", "tmpl", "underscore", "jquery.adhesion", "hints"]);
  735
+        Khan.require(["answer-types", "tmpl", "underscore", "jquery.adhesion", "hints", "calculator"]);
736 736
 
737 737
         Khan.require(document.documentElement.getAttribute("data-require"));
738 738
 
@@ -1149,6 +1149,11 @@ var Khan = (function() {
1149 1149
             $(".hint-box").remove();
1150 1150
         }
1151 1151
 
  1152
+        // Remove the calculator if it's not specifically allowed for this problem
  1153
+        if (problem.data("calculator") == null) {
  1154
+            $("#calculator").remove();
  1155
+        }
  1156
+
1152 1157
         debugLog("removed hints from DOM");
1153 1158
 
1154 1159
         // Evaluate any inline script tags in this exercise's source
@@ -1244,6 +1249,9 @@ var Khan = (function() {
1244 1249
             // Focus the first input
1245 1250
             // Use .select() and on a delay to make IE happy
1246 1251
             var firstInput = solutionarea.find(":input").first();
  1252
+            if ($(".calculator input").length) {
  1253
+                firstInput = $(".calculator input");
  1254
+            }
1247 1255
             setTimeout(function() {
1248 1256
                 if (!firstInput.is(":disabled")) {
1249 1257
                     firstInput.focus();
@@ -2138,6 +2146,92 @@ var Khan = (function() {
2138 2146
             return false;
2139 2147
         }
2140 2148
 
  2149
+        function initializeCalculator() {
  2150
+            var calculator = $(".calculator"),
  2151
+                history = calculator.children(".history"),
  2152
+                inputRow = history.children(".row.input"),
  2153
+                input = inputRow.children("input"),
  2154
+                buttons = calculator.find("a"),
  2155
+                lastInstr = "",
  2156
+                ans;
  2157
+
  2158
+            var evaluate = function() {
  2159
+                var instr = input.val();
  2160
+                var row, indiv, output, outstr, outdiv;
  2161
+                if ($.trim(instr) !== "") {
  2162
+                    lastInstr = instr;
  2163
+                    row = $("<div>").addClass("row");
  2164
+                    indiv = $("<div>").addClass("input").text(instr).appendTo(row);
  2165
+                    try {
  2166
+                        output = ans = Calculator.calculate(instr, ans);
  2167
+                        if (typeof output === "number") {
  2168
+                            outstr = Math.round(output * 1000000000) / 1000000000;
  2169
+                        } else {
  2170
+                            outstr = output;
  2171
+                        }
  2172
+                    } catch (e) {
  2173
+                        if (e instanceof Calculator.CalculatorError) {
  2174
+                            outstr = e.message;
  2175
+                        } else {
  2176
+                            throw e;
  2177
+                        }
  2178
+                    }
  2179
+                    outdiv = $("<div>").addClass("output").text(outstr).appendTo(row);
  2180
+                    inputRow.before(row);
  2181
+                }
  2182
+
  2183
+                input.val("");
  2184
+                input[0].scrollIntoView(false);
  2185
+            };
  2186
+
  2187
+            input.on("keyup", function(e) {
  2188
+                if (e.which === 13) {
  2189
+                    evaluate();
  2190
+                    return false;
  2191
+                } else if (e.which === 38) {
  2192
+                    if (lastInstr !== "") {
  2193
+                        input.val(lastInstr);
  2194
+                    }
  2195
+                    return false;
  2196
+                }
  2197
+            });
  2198
+
  2199
+            buttons.on("click", function() {
  2200
+                var jel = $(this),
  2201
+                    behavior = jel.data("behavior");
  2202
+
  2203
+                if (behavior != null) {
  2204
+                    if (behavior === "bs") {
  2205
+                        var val = input.val();
  2206
+                        input.val(val.slice(0, val.length - 1));
  2207
+                    } else if (behavior === "clear") {
  2208
+                        input.val("");
  2209
+                        history.children().not(inputRow).remove();
  2210
+                    } else if (behavior === "angle-mode") {
  2211
+                        Calculator.angleMode = Calculator.angleMode === "DEG" ?
  2212
+                            "RAD" : "DEG";
  2213
+                        jel.html((Calculator.angleMode === "DEG" ? "<br>" : "")
  2214
+                            + Calculator.angleMode);
  2215
+                    } else if (behavior === "evaluate") {
  2216
+                        evaluate();
  2217
+                    }
  2218
+                } else {
  2219
+                    var text = jel.data("text") || jel.text();
  2220
+                    input.val(input.val() + text);
  2221
+                }
  2222
+
  2223
+                input.focus();
  2224
+                return false;
  2225
+            });
  2226
+
  2227
+            $(Khan).on("gotoNextProblem", function(event) {
  2228
+                input.val("");
  2229
+                history.children().not(inputRow).remove();
  2230
+            });
  2231
+        };
  2232
+
  2233
+        initializeCalculator();
  2234
+
2141 2235
         // Watch for when the next button is clicked
2142 2236
         $("#next-question-button").click(function(ev) {
2143 2237
             nextProblem(1);
454  utils/calculator.js
... ...
@@ -0,0 +1,454 @@
  1
+/* Jison generated parser */
  2
+var Calculator = (function(){
  3
+var parser = {trace: function trace() { },
  4
+yy: {},
  5
+symbols_: {"error":2,"expressions":3,"e":4,"EOF":5,"+":6,"-":7,"*":8,"/":9,"^":10,"!":11,"FN":12,"(":13,")":14,"ANS":15,"NUM":16,"$accept":0,"$end":1},
  6
+terminals_: {2:"error",5:"EOF",6:"+",7:"-",8:"*",9:"/",10:"^",11:"!",12:"FN",13:"(",14:")",15:"ANS",16:"NUM"},
  7
+productions_: [0,[3,2],[4,3],[4,3],[4,3],[4,3],[4,3],[4,2],[4,2],[4,4],[4,3],[4,1],[4,1]],
  8
+performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
  9
+
  10
+var $0 = $$.length - 1;
  11
+switch (yystate) {
  12
+case 1:return $$[$0-1];
  13
+break;
  14
+case 2:this.$ = ["+", $$[$0-2], $$[$0]];
  15
+break;
  16
+case 3:this.$ = ["-", $$[$0-2], $$[$0]];
  17
+break;
  18
+case 4:this.$ = ["*", $$[$0-2], $$[$0]];
  19
+break;
  20
+case 5:this.$ = ["/", $$[$0-2], $$[$0]];
  21
+break;
  22
+case 6:this.$ = ["^", $$[$0-2], $$[$0]];
  23
+break;
  24
+case 7:this.$ = ["!", $$[$0-1]];
  25
+break;
  26
+case 8:this.$ = ["-", $$[$0]];
  27
+break;
  28
+case 9:this.$ = [$$[$0-3], $$[$0-1]];
  29
+break;
  30
+case 10:this.$ = $$[$0-1];
  31
+break;
  32
+case 11:this.$ = $$[$0];
  33
+break;
  34
+case 12:this.$ = Number(yytext);
  35
+break;
  36
+}
  37
+},
  38
+table: [{3:1,4:2,7:[1,3],12:[1,4],13:[1,5],15:[1,6],16:[1,7]},{1:[3]},{5:[1,8],6:[1,9],7:[1,10],8:[1,11],9:[1,12],10:[1,13],11:[1,14]},{4:15,7:[1,3],12:[1,4],13:[1,5],15:[1,6],16:[1,7]},{13:[1,16]},{4:17,7:[1,3],12:[1,4],13:[1,5],15:[1,6],16:[1,7]},{5:[2,11],6:[2,11],7:[2,11],8:[2,11],9:[2,11],10:[2,11],11:[2,11],14:[2,11]},{5:[2,12],6:[2,12],7:[2,12],8:[2,12],9:[2,12],10:[2,12],11:[2,12],14:[2,12]},{1:[2,1]},{4:18,7:[1,3],12:[1,4],13:[1,5],15:[1,6],16:[1,7]},{4:19,7:[1,3],12:[1,4],13:[1,5],15:[1,6],16:[1,7]},{4:20,7:[1,3],12:[1,4],13:[1,5],15:[1,6],16:[1,7]},{4:21,7:[1,3],12:[1,4],13:[1,5],15:[1,6],16:[1,7]},{4:22,7:[1,3],12:[1,4],13:[1,5],15:[1,6],16:[1,7]},{5:[2,7],6:[2,7],7:[2,7],8:[2,7],9:[2,7],10:[2,7],11:[2,7],14:[2,7]},{5:[2,8],6:[1,9],7:[1,10],8:[1,11],9:[1,12],10:[1,13],11:[1,14],14:[2,8]},{4:23,7:[1,3],12:[1,4],13:[1,5],15:[1,6],16:[1,7]},{6:[1,9],7:[1,10],8:[1,11],9:[1,12],10:[1,13],11:[1,14],14:[1,24]},{5:[2,2],6:[2,2],7:[2,2],8:[1,11],9:[1,12],10:[1,13],11:[1,14],14:[2,2]},{5:[2,3],6:[2,3],7:[2,3],8:[1,11],9:[1,12],10:[1,13],11:[1,14],14:[2,3]},{5:[2,4],6:[2,4],7:[2,4],8:[2,4],9:[2,4],10:[1,13],11:[1,14],14:[2,4]},{5:[2,5],6:[2,5],7:[2,5],8:[2,5],9:[2,5],10:[1,13],11:[1,14],14:[2,5]},{5:[2,6],6:[2,6],7:[2,6],8:[2,6],9:[2,6],10:[2,6],11:[1,14],14:[2,6]},{6:[1,9],7:[1,10],8:[1,11],9:[1,12],10:[1,13],11:[1,14],14:[1,25]},{5:[2,10],6:[2,10],7:[2,10],8:[2,10],9:[2,10],10:[2,10],11:[2,10],14:[2,10]},{5:[2,9],6:[2,9],7:[2,9],8:[2,9],9:[2,9],10:[2,9],11:[2,9],14:[2,9]}],
  39
+defaultActions: {8:[2,1]},
  40
+parseError: function parseError(str, hash) {
  41
+    throw new Error(str);
  42
+},
  43
+parse: function parse(input) {
  44
+    var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
  45
+    this.lexer.setInput(input);
  46
+    this.lexer.yy = this.yy;
  47
+    this.yy.lexer = this.lexer;
  48
+    this.yy.parser = this;
  49
+    if (typeof this.lexer.yylloc == "undefined")
  50
+        this.lexer.yylloc = {};
  51
+    var yyloc = this.lexer.yylloc;
  52
+    lstack.push(yyloc);
  53
+    var ranges = this.lexer.options && this.lexer.options.ranges;
  54
+    if (typeof this.yy.parseError === "function")
  55
+        this.parseError = this.yy.parseError;
  56
+    function popStack(n) {
  57
+        stack.length = stack.length - 2 * n;
  58
+        vstack.length = vstack.length - n;
  59
+        lstack.length = lstack.length - n;
  60
+    }
  61
+    function lex() {
  62
+        var token;
  63
+        token = self.lexer.lex() || 1;
  64
+        if (typeof token !== "number") {
  65
+            token = self.symbols_[token] || token;
  66
+        }
  67
+        return token;
  68
+    }
  69
+    var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
  70
+    while (true) {
  71
+        state = stack[stack.length - 1];
  72
+        if (this.defaultActions[state]) {
  73
+            action = this.defaultActions[state];
  74
+        } else {
  75
+            if (symbol === null || typeof symbol == "undefined") {
  76
+                symbol = lex();
  77
+            }
  78
+            action = table[state] && table[state][symbol];
  79
+        }
  80
+        if (typeof action === "undefined" || !action.length || !action[0]) {
  81
+            var errStr = "";
  82
+            if (!recovering) {
  83
+                expected = [];
  84
+                for (p in table[state])
  85
+                    if (this.terminals_[p] && p > 2) {
  86
+                        expected.push("'" + this.terminals_[p] + "'");
  87
+                    }
  88
+                if (this.lexer.showPosition) {
  89
+                    errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'";
  90
+                } else {
  91
+                    errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'");
  92
+                }
  93
+                this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
  94
+            }
  95
+        }
  96
+        if (action[0] instanceof Array && action.length > 1) {
  97
+            throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
  98
+        }
  99
+        switch (action[0]) {
  100
+        case 1:
  101
+            stack.push(symbol);
  102
+            vstack.push(this.lexer.yytext);
  103
+            lstack.push(this.lexer.yylloc);
  104
+            stack.push(action[1]);
  105
+            symbol = null;
  106
+            if (!preErrorSymbol) {
  107
+                yyleng = this.lexer.yyleng;
  108
+                yytext = this.lexer.yytext;
  109
+                yylineno = this.lexer.yylineno;
  110
+                yyloc = this.lexer.yylloc;
  111
+                if (recovering > 0)
  112
+                    recovering--;
  113
+            } else {
  114
+                symbol = preErrorSymbol;
  115
+                preErrorSymbol = null;
  116
+            }
  117
+            break;
  118
+        case 2:
  119
+            len = this.productions_[action[1]][1];
  120
+            yyval.$ = vstack[vstack.length - len];
  121
+            yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column};
  122
+            if (ranges) {
  123
+                yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]];
  124
+            }
  125
+            r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
  126
+            if (typeof r !== "undefined") {
  127
+                return r;
  128
+            }
  129
+            if (len) {
  130
+                stack = stack.slice(0, -1 * len * 2);
  131
+                vstack = vstack.slice(0, -1 * len);
  132
+                lstack = lstack.slice(0, -1 * len);
  133
+            }
  134
+            stack.push(this.productions_[action[1]][0]);
  135
+            vstack.push(yyval.$);
  136
+            lstack.push(yyval._$);
  137
+            newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
  138
+            stack.push(newState);
  139
+            break;
  140
+        case 3:
  141
+            return true;
  142
+        }
  143
+    }
  144
+    return true;
  145
+}
  146
+};
  147
+/* Jison generated lexer */
  148
+var lexer = (function(){
  149
+var lexer = ({EOF:1,
  150
+parseError:function parseError(str, hash) {
  151
+        if (this.yy.parser) {
  152
+            this.yy.parser.parseError(str, hash);
  153
+        } else {
  154
+            throw new Error(str);
  155
+        }
  156
+    },
  157
+setInput:function (input) {
  158
+        this._input = input;
  159
+        this._more = this._less = this.done = false;
  160
+        this.yylineno = this.yyleng = 0;
  161
+        this.yytext = this.matched = this.match = '';
  162
+        this.conditionStack = ['INITIAL'];
  163
+        this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
  164
+        if (this.options.ranges) this.yylloc.range = [0,0];
  165
+        this.offset = 0;
  166
+        return this;
  167
+    },
  168
+input:function () {
  169
+        var ch = this._input[0];
  170
+        this.yytext += ch;
  171
+        this.yyleng++;
  172
+        this.offset++;
  173
+        this.match += ch;
  174
+        this.matched += ch;
  175
+        var lines = ch.match(/(?:\r\n?|\n).*/g);
  176
+        if (lines) {
  177
+            this.yylineno++;
  178
+            this.yylloc.last_line++;
  179
+        } else {
  180
+            this.yylloc.last_column++;
  181
+        }
  182
+        if (this.options.ranges) this.yylloc.range[1]++;
  183
+
  184
+        this._input = this._input.slice(1);
  185
+        return ch;
  186
+    },
  187
+unput:function (ch) {
  188
+        var len = ch.length;
  189
+        var lines = ch.split(/(?:\r\n?|\n)/g);
  190
+
  191
+        this._input = ch + this._input;
  192
+        this.yytext = this.yytext.substr(0, this.yytext.length-len-1);
  193
+        //this.yyleng -= len;
  194
+        this.offset -= len;
  195
+        var oldLines = this.match.split(/(?:\r\n?|\n)/g);
  196
+        this.match = this.match.substr(0, this.match.length-1);
  197
+        this.matched = this.matched.substr(0, this.matched.length-1);
  198
+
  199
+        if (lines.length-1) this.yylineno -= lines.length-1;
  200
+        var r = this.yylloc.range;
  201
+
  202
+        this.yylloc = {first_line: this.yylloc.first_line,
  203
+          last_line: this.yylineno+1,
  204
+          first_column: this.yylloc.first_column,
  205
+          last_column: lines ?
  206
+              (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length:
  207
+              this.yylloc.first_column - len
  208
+          };
  209
+
  210
+        if (this.options.ranges) {
  211
+            this.yylloc.range = [r[0], r[0] + this.yyleng - len];
  212
+        }
  213
+        return this;
  214
+    },
  215
+more:function () {
  216
+        this._more = true;
  217
+        return this;
  218
+    },
  219
+less:function (n) {
  220
+        this.unput(this.match.slice(n));
  221
+    },
  222
+pastInput:function () {
  223
+        var past = this.matched.substr(0, this.matched.length - this.match.length);
  224
+        return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
  225
+    },
  226
+upcomingInput:function () {
  227
+        var next = this.match;
  228
+        if (next.length < 20) {
  229
+            next += this._input.substr(0, 20-next.length);
  230
+        }
  231
+        return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
  232
+    },
  233
+showPosition:function () {
  234
+        var pre = this.pastInput();
  235
+        var c = new Array(pre.length + 1).join("-");
  236
+        return pre + this.upcomingInput() + "\n" + c+"^";
  237
+    },
  238
+next:function () {
  239
+        if (this.done) {
  240
+            return this.EOF;
  241
+        }
  242
+        if (!this._input) this.done = true;
  243
+
  244
+        var token,
  245
+            match,
  246
+            tempMatch,
  247
+            index,
  248
+            col,
  249
+            lines;
  250
+        if (!this._more) {
  251
+            this.yytext = '';
  252
+            this.match = '';
  253
+        }
  254
+        var rules = this._currentRules();
  255
+        for (var i=0;i < rules.length; i++) {
  256
+            tempMatch = this._input.match(this.rules[rules[i]]);
  257
+            if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
  258
+                match = tempMatch;
  259
+                index = i;
  260
+                if (!this.options.flex) break;
  261
+            }
  262
+        }
  263
+        if (match) {
  264
+            lines = match[0].match(/(?:\r\n?|\n).*/g);
  265
+            if (lines) this.yylineno += lines.length;
  266
+            this.yylloc = {first_line: this.yylloc.last_line,
  267
+                           last_line: this.yylineno+1,
  268
+                           first_column: this.yylloc.last_column,
  269
+                           last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length};
  270
+            this.yytext += match[0];
  271
+            this.match += match[0];
  272
+            this.matches = match;
  273
+            this.yyleng = this.yytext.length;
  274
+            if (this.options.ranges) {
  275
+                this.yylloc.range = [this.offset, this.offset += this.yyleng];
  276
+            }
  277
+            this._more = false;
  278
+            this._input = this._input.slice(match[0].length);
  279
+            this.matched += match[0];
  280
+            token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);
  281
+            if (this.done && this._input) this.done = false;
  282
+            if (token) return token;
  283
+            else return;
  284
+        }
  285
+        if (this._input === "") {
  286
+            return this.EOF;
  287
+        } else {
  288
+            return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
  289
+                    {text: "", token: null, line: this.yylineno});
  290
+        }
  291
+    },
  292
+lex:function lex() {
  293
+        var r = this.next();
  294
+        if (typeof r !== 'undefined') {
  295
+            return r;
  296
+        } else {
  297
+            return this.lex();
  298
+        }
  299
+    },
  300
+begin:function begin(condition) {
  301
+        this.conditionStack.push(condition);
  302
+    },
  303
+popState:function popState() {
  304
+        return this.conditionStack.pop();
  305
+    },
  306
+_currentRules:function _currentRules() {
  307
+        return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
  308
+    },
  309
+topState:function () {
  310
+        return this.conditionStack[this.conditionStack.length-2];
  311
+    },
  312
+pushState:function begin(condition) {
  313
+        this.begin(condition);
  314
+    }});
  315
+lexer.options = {};
  316
+lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
  317
+
  318
+var YYSTATE=YY_START
  319
+switch($avoiding_name_collisions) {
  320
+case 0:/* skip whitespace */
  321
+break;
  322
+case 1:return 16
  323
+break;
  324
+case 2:return 6
  325
+break;
  326
+case 3:return 7
  327
+break;
  328
+case 4:return 8
  329
+break;
  330
+case 5:return 9
  331
+break;
  332
+case 6:return 10
  333
+break;
  334
+case 7:return 11
  335
+break;
  336
+case 8:return 13
  337
+break;
  338
+case 9:return 14
  339
+break;
  340
+case 10:return 15
  341
+break;
  342
+case 11:return 12
  343
+break;
  344
+case 12:return 5
  345
+break;
  346
+case 13:return 'INVALID'
  347
+break;
  348
+}
  349
+};
  350
+lexer.rules = [/^(?:\s+)/,/^(?:([\.0-9]+)\b)/,/^(?:\+)/,/^(?:-)/,/^(?:\*)/,/^(?:\/)/,/^(?:\^)/,/^(?:!)/,/^(?:\()/,/^(?:\))/,/^(?:ans\b)/,/^(?:[a-z]+)/,/^(?:$)/,/^(?:.)/];
  351
+lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],"inclusive":true}};
  352
+return lexer;})()
  353
+parser.lexer = lexer;
  354
+function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser;
  355
+return new Parser;
  356
+})();var Calculator = (function(parser) {
  357
+    var CalculatorError = function(message) {
  358
+        this.message = message;
  359
+    };
  360
+    CalculatorError.prototype = new Error();
  361
+    CalculatorError.prototype.constructor = CalculatorError;
  362
+
  363
+    parser.parseError = function parseError(str, hash) {
  364
+        throw new CalculatorError("err");
  365
+    };
  366
+
  367
+    return _.bindAll({
  368
+        angleMode: "DEG",
  369
+        parser: parser,
  370
+        parse: _.bind(parser.parse, parser),
  371
+
  372
+        evaluate: function(tree, ans) {
  373
+            var toRad = function(ang) {
  374
+                if (Calculator.angleMode === "DEG") {
  375
+                    return ang * Math.PI / 180;
  376
+                }
  377
+                return ang;
  378
+            };
  379
+            var fromRad = function(ang) {
  380
+                if (Calculator.angleMode === "DEG") {
  381
+                    return ang / Math.PI * 180;
  382
+                }
  383
+                return ang;
  384
+            };
  385
+            if (tree === "ans") {
  386
+                if (ans != null) {
  387
+                    return ans;
  388
+                } else {
  389
+                    throw new CalculatorError("Invalid variable ans");
  390
+                }
  391
+            } else if (_.isNumber(tree)) {
  392
+                return tree;
  393
+            } else if (_.isArray(tree)) {
  394
+                var fns = {
  395
+                    "+": function(a, b) { return a + b; },
  396
+                    "-": function(a, b) { return b == null ? -a : a - b; },
  397
+                    "*": function(a, b) { return a * b; },
  398
+                    "/": function(a, b) { return a / b; },
  399
+                    "^": function(a, b) { return Math.pow(a, b); },
  400
+                    "!": function f(a) { return a <= 1 ? 1 : a * f(a - 1); },
  401
+                    sqrt: function(a) { return Math.pow(a, 0.5); },
  402
+                    sin: function(a) { return Math.sin(toRad(a)); },
  403
+                    cos: function(a) { return Math.cos(toRad(a)); },
  404
+                    tan: function(a) {
  405
+                        var ans = Math.tan(toRad(a));
  406
+                        if (isNaN(ans) || Math.abs(ans) > Math.pow(2, 53)) {
  407
+                            throw new CalculatorError("undefined");
  408
+                        }
  409
+                        return ans;
  410
+                    },
  411
+                    asin: function(a) {
  412
+                        var ans = fromRad(Math.asin(a));
  413
+                        if (isNaN(ans)) {
  414
+                            throw new CalculatorError("undefined");
  415
+                        }
  416
+                        return ans;
  417
+                    },
  418
+                    acos: function(a) {
  419
+                        var ans = fromRad(Math.acos(a));
  420
+                        if (isNaN(ans)) {
  421
+                            throw new CalculatorError("undefined");
  422
+                        }
  423
+                        return ans;
  424
+                    },
  425
+                    atan: function(a) {
  426
+                        var ans = fromRad(Math.atan(a));
  427
+                        if (isNaN(ans)) {
  428
+                            throw new CalculatorError("undefined");
  429
+                        }
  430
+                        return ans;
  431
+                    }
  432
+                };
  433
+
  434
+                if (tree[0] in fns) {
  435
+                    var self = this;
  436
+                    return fns[tree[0]].apply(
  437
+                        this, _.map(tree.slice(1), function(t) {
  438
+                            return self.evaluate(t, ans); }));
  439
+                } else {
  440
+                    throw new CalculatorError("err");
  441
+                }
  442
+            } else {
  443
+                throw new CalculatorError(
  444
+                    "Invalid type " + Object.prototype.toString.call(tree));
  445
+            }
  446
+        },
  447
+
  448
+        calculate: function(str, ans) {
  449
+            var tree = this.parse(str);
  450
+            return this.evaluate(tree, ans);
  451
+        },
  452
+        CalculatorError: CalculatorError
  453
+    }, "evaluate", "calculate");
  454
+})(Calculator);

0 notes on commit 8ba5ce5

Please sign in to comment.
Something went wrong with that request. Please try again.