Permalink
Browse files

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...
1 parent 064281e commit 8ba5ce5566b9e5c3122a4ea4597cf1461ea9fc44 @beneater beneater committed Sep 21, 2012
View
@@ -0,0 +1,4 @@
+utils/calculator.js: build/calculator/calculator.jison build/calculator/calculator-tail.js
+ jison -m js build/calculator/calculator.jison -o utils/Calculator.js
+ cat build/calculator/calculator-tail.js >>utils/Calculator.js
+ mv utils/Calculator.js utils/calculator.js
@@ -0,0 +1,99 @@
+var Calculator = (function(parser) {
+ var CalculatorError = function(message) {
+ this.message = message;
+ };
+ CalculatorError.prototype = new Error();
+ CalculatorError.prototype.constructor = CalculatorError;
+
+ parser.parseError = function parseError(str, hash) {
+ throw new CalculatorError("err");
+ };
+
+ return _.bindAll({
+ angleMode: "DEG",
+ parser: parser,
+ parse: _.bind(parser.parse, parser),
+
+ evaluate: function(tree, ans) {
+ var toRad = function(ang) {
+ if (Calculator.angleMode === "DEG") {
+ return ang * Math.PI / 180;
+ }
+ return ang;
+ };
+ var fromRad = function(ang) {
+ if (Calculator.angleMode === "DEG") {
+ return ang / Math.PI * 180;
+ }
+ return ang;
+ };
+ if (tree === "ans") {
+ if (ans != null) {
+ return ans;
+ } else {
+ throw new CalculatorError("Invalid variable ans");
+ }
+ } else if (_.isNumber(tree)) {
+ return tree;
+ } else if (_.isArray(tree)) {
+ var fns = {
+ "+": function(a, b) { return a + b; },
+ "-": function(a, b) { return b == null ? -a : a - b; },
+ "*": function(a, b) { return a * b; },
+ "/": function(a, b) { return a / b; },
+ "^": function(a, b) { return Math.pow(a, b); },
+ "!": function f(a) { return a <= 1 ? 1 : a * f(a - 1); },
+ sqrt: function(a) { return Math.pow(a, 0.5); },
+ sin: function(a) { return Math.sin(toRad(a)); },
+ cos: function(a) { return Math.cos(toRad(a)); },
+ tan: function(a) {
+ var ans = Math.tan(toRad(a));
+ if (isNaN(ans) || Math.abs(ans) > Math.pow(2, 53)) {
+ throw new CalculatorError("undefined");
+ }
+ return ans;
+ },
+ asin: function(a) {
+ var ans = fromRad(Math.asin(a));
+ if (isNaN(ans)) {
+ throw new CalculatorError("undefined");
+ }
+ return ans;
+ },
+ acos: function(a) {
+ var ans = fromRad(Math.acos(a));
+ if (isNaN(ans)) {
+ throw new CalculatorError("undefined");
+ }
+ return ans;
+ },
+ atan: function(a) {
+ var ans = fromRad(Math.atan(a));
+ if (isNaN(ans)) {
+ throw new CalculatorError("undefined");
+ }
+ return ans;
+ }
+ };
+
+ if (tree[0] in fns) {
+ var self = this;
+ return fns[tree[0]].apply(
+ this, _.map(tree.slice(1), function(t) {
+ return self.evaluate(t, ans); }));
+ } else {
+ throw new CalculatorError("err");
+ }
+ } else {
+ throw new CalculatorError(
+ "Invalid type " + Object.prototype.toString.call(tree));
+ }
+ },
+
+ calculate: function(str, ans) {
+ var tree = this.parse(str);
+ return this.evaluate(tree, ans);
+ },
+ CalculatorError: CalculatorError
+ }, "evaluate", "calculate");
+})(Calculator);
@@ -0,0 +1,65 @@
+
+/* description: Parses end executes mathematical expressions. */
+
+/* lexical grammar */
+%lex
+%%
+
+\s+ /* skip whitespace */
+([\.0-9]+)\b return 'NUM'
+"+" return '+'
+"-" return '-'
+"*" return '*'
+"/" return '/'
+"^" return '^'
+"!" return '!'
+"(" return '('
+")" return ')'
+"ans" return 'ANS'
+[a-z]+ return 'FN'
+<<EOF>> return 'EOF'
+. return 'INVALID'
+
+/lex
+
+/* operator associations and precedence */
+
+%right UMINUS
+%left '+' '-'
+%left '*' '/'
+%left '^'
+%right '!'
+
+%start expressions
+
+%% /* language grammar */
+
+expressions
+ : e EOF
+ {return $1;}
+ ;
+
+e
+ : e '+' e
+ {$$ = ["+", $1, $3];}
+ | e '-' e
+ {$$ = ["-", $1, $3];}
+ | e '*' e
+ {$$ = ["*", $1, $3];}
+ | e '/' e
+ {$$ = ["/", $1, $3];}
+ | e '^' e
+ {$$ = ["^", $1, $3];}
+ | e '!'
+ {$$ = ["!", $1];}
+ | '-' e %prec UMINUS
+ {$$ = ["-", $2];}
+ | FN '(' e ')'
+ {$$ = [$1, $3];}
+ | '(' e ')'
+ {$$ = $2;}
+ | ANS
+ {$$ = $1;}
+ | NUM
+ {$$ = Number(yytext);}
+ ;
View
@@ -121,6 +121,73 @@ body.debug .graphie { outline: 1px dashed #fdd; }
color: #fff;
text-decoration: none;
}
+
+#answer_area .calculator .history {
+ font: 14px/1.5 Menlo, Monaco, Courier, monospace;
+ margin: 0 0 10px;
+ width: 183px;
+ max-height: 150px;
+ overflow-y: scroll;
+ overflow-x: hidden;
+}
+
+#answer_area .calculator .history .output {
+ text-align: right;
+}
+
+#answer_area .calculator .history input {
+ box-sizing: border-box;
+ display: block;
+ font: inherit;
+ margin: 5px 3px 3px;
+ width: 169px;
+ padding-right: 25px;
+}
+
+#answer_area .calculator .history .input {
+ position: relative;
+}
+
+#answer_area .calculator .history .status a {
+ position: absolute;
+ font: 10px/1.5 Menlo, Monaco, Courier, monospace;
+ color: #999;
+ text-decoration: none;
+ line-height: 10px;
+ height: 19px;
+ top: 5px;
+ right: 6px;
+}
+
+#answer_area .calculator .keypad .row {
+ margin: 5px 0;
+}
+
+#answer_area .calculator .keypad .row a {
+ background: #ccc;
+ border-radius: 2px;
+ color: #000;
+ display: inline-block;
+ margin: 0 5px 0 0;
+ text-align: center;
+ text-decoration: none;
+ width: 31px;
+}
+
+#answer_area .calculator .keypad .row a.dark {
+ background: #aaa;
+}
+
+#answer_area .calculator .keypad a sup {
+ vertical-align: super;
+ font-size: 85%;
+ line-height: 0;
+}
+
+#answer_area .calculator .keypad a.wide {
+ width: 67px;
+}
+
#solutionarea {
min-height: 35px;
padding: 10px;
@@ -56,6 +56,34 @@
</div>
</div>
</form>
+ <div id="calculator" class="info-box">
+ <span class="info-box-header">Calculator</span>
+ <div class="calculator">
+ <div class="history fancy-scrollbar">
+ <div class="row input"><input type="text"><div class="status"><a href="#" data-behavior="angle-mode"><br>DEG</a></div></div>
+ </div>
+ <div class="keypad">
+ <div class="row">
+ <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>
+ </div>
+ <div class="row">
+ <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>
+ </div>
+ <div class="row">
+ <a href="#" class="dark">7</a><a href="#" class="dark">8</a><a href="#" class="dark">9</a><a href="#">(</a><a href="#">)</a>
+ </div>
+ <div class="row">
+ <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>
+ </div>
+ <div class="row">
+ <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>
+ </div>
+ <div class="row">
+ <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>
+ </div>
+ </div>
+ </div>
+ </div>
<div id="issue" class="info-box" style="display:none;">
<span class="info-box-header">Report a Problem</span>
<span id="issue-status" class="info-box-sub-description"></span>
Oops, something went wrong.

0 comments on commit 8ba5ce5

Please sign in to comment.