diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/calculator-backends.js b/calculator-backends.js new file mode 100644 index 0000000..86395f4 --- /dev/null +++ b/calculator-backends.js @@ -0,0 +1,393 @@ +// # calculator-backends.js +// +// more things to do with a simple calculator parser +// +// [calculator-parser.js](calculator-parser.html) contains a simple parser. +// It contains enough code that you can actually do some basic math with it. +// But what else can you do with a parser? +// +// This file contains seven different applications of +// the calculator parser. +// +// [Try them out.](../calculator.html) + +// ## Interpreters + +// ### 1. Evaluate using floating-point numbers + +// This behaves like a stripped-down version of JavaScript `eval()`. +function evaluateAsFloat(code) { + var calculator = { + number: function (s) { return parseInt(s); }, + add: function (a, b) { return a + b; }, + sub: function (a, b) { return a - b; }, + mul: function (a, b) { return a * b; }, + div: function (a, b) { return a / b; }, + _variables: Object.create(null), + name: function (name) { return this._variables[name] || 0; } + }; + + calculator._variables.e = Math.E; + calculator._variables.pi = Math.PI; + + return parse(code, calculator); +} + + +// ### 2. Evaluate using precise fraction arithmetic +// +// Our little language is a tiny subset of JavaScript. But that doesn’t meant +// it has to behave exactly like JavaScript. This is our language. +// It can behave however we want. + +// So how about a calculator that does arbitrary precision arithmetic? +// Let’s start by defining a `Fraction` class... + +var BigInteger = require('biginteger').BigInteger; + +function gcd(a, b) { + while (!b.isZero()) { + var tmp = a; + a = b; + b = tmp.remainder(b); + } + return a; +} + +function Fraction(n, d) { + if (d === undefined) + d = new BigInteger(1); + var x = gcd(n, d); + this.n = n.divide(x); + this.d = d.divide(x); +} + +// …and some Fraction methods. You learned these techniques in grade school, +// though you may have forgotten some of them. +Fraction.prototype = { + add: function (x) { + return new Fraction(this.n.multiply(x.d).add(x.n.multiply(this.d)), + this.d.multiply(x.d)); + }, + negate: function (x) { + return new Fraction(this.n.negate(), this.d); + }, + sub: function (x) { + return this.add(x.negate()); + }, + mul: function (x) { + return new Fraction(this.n.multiply(x.n), this.d.multiply(x.d)); + }, + div: function (x) { + return new Fraction(this.n.multiply(x.d), this.d.multiply(x.n)); + }, + toString: function () { + var ns = this.n.toString(), ds = this.d.toString(); + if (ds === "1") + return ns; + else + return ns + "/" + ds; + } +}; + +// Now simply write an `out` object that computes the results using `Fraction` +// objects rather than JavaScript numbers. It’s almost too easy. +function evaluateAsFraction(code) { + var fractionCalculator = { + number: function (s) { return new Fraction(new BigInteger(s)); }, + add: function (a, b) { return a.add(b); }, + sub: function (a, b) { return a.sub(b); }, + mul: function (a, b) { return a.mul(b); }, + div: function (a, b) { return a.div(b); }, + name: function (name) { throw new SyntaxError("no variables in fraction mode, sorry"); } + }; + return parse(code, fractionCalculator); +} + +// Our tiny programming language is suddenly doing something JavaScript itself +// doesn’t do: arithmetic with exact (not floating-point) results. Tests: +assert.strictEqual(evaluateAsFraction("1 / 3").toString(), "1/3"); +assert.strictEqual(evaluateAsFraction("(2/3) * (3/2)").toString(), "1"); +assert.strictEqual(evaluateAsFraction("1/7 + 4/7 + 2/7").toString(), "1"); +assert.strictEqual( + evaluateAsFraction("5996788328646786302319492/2288327879043508396784319").toString(), + "324298349324/123749732893"); + + +// ## Code as data + +// ### 3. Convert to DOM +// +// Both examples above compute a result. But that isn’t the only thing you can +// do with language. Let’s make a program that doesn’t compute anything at all: +// it simply spits out DOM nodes that show how the program would look in +// Scratch. (!) + +// Helper function to create DOM elements. +function span(className, contents) { + var e = document.createElement("span"); + e.className = className; + for (var i = 0; i < contents.length; i++) { + var kid = contents[i]; + if (typeof kid === "string") + kid = document.createTextNode(kid); + e.appendChild(kid); + } + return e; +} + +// Yet another pluggable `out` object. +function convertToDOM(code) { + var spanBuilder = { + number: function (s) { return span("num", [s]); }, + add: function (a, b) { return span("expr", [a, "+", b]); }, + sub: function (a, b) { return span("expr", [a, "\u2212", b]); }, // − + mul: function (a, b) { return span("expr", [a, "\u00d7", b]); }, // × + div: function (a, b) { return span("expr", [a, "\u00f7", b]); }, // ÷ + name: function (name) { return span("var", [name]); } + }; + return parse(code, spanBuilder); +} + + +// ### 4. Convert to JSON +// +// Let’s make one that builds a tree describing the input formula. This is +// called an abstract syntax tree, or AST. **This is most likely what you would +// do if you planned to make your own programming language.** It was once +// common to parse and emit code in a single pass. Languages were carefully +// designed to make sure that was possible. Today, there’s really no reason not +// to build a complete AST or other intermediate form, then emit code in a +// second pass. + +// Each method simply returns a new JS object. +function convertToAST(code) { + var astBuilder = { + number: function (s) { return {type: "number", value: s}; }, + add: function (a, b) { return {type: "add", left: a, right: b}; }, + sub: function (a, b) { return {type: "sub", left: a, right: b}; }, + mul: function (a, b) { return {type: "mul", left: a, right: b}; }, + div: function (a, b) { return {type: "div", left: a, right: b}; }, + name: function (name) { return {type: "name", name: name}; } + }; + return parse(code, astBuilder); +} + +// And test it. +assert.deepEqual( + convertToAST("(1 + 2) / 3"), + { + type: "div", + left: { + type: "add", + left: {type: "number", value: "1"}, + right: {type: "number", value: "2"} + }, + right: {type: "number", value: "3"} + }); + + +// ### 5. MathML output +// +// One more riff on this theme: How about generating beautiful MathML output? +// (Unfortunately, some browsers still do not support MathML. Firefox does.) + +// The hardest part of this was figuring out how to make MathML elements. +// Here’s some code to help with that. +var mathml = "http://www.w3.org/1998/Math/MathML"; + +function mo(s) { + var e = document.createElementNS(mathml, "mo"); + var t = document.createTextNode(s); + e.appendChild(t); + return {prec: 3, element: e}; +} + +// Create a new MathML DOM element of the specified type and contents. +// `precedence` is used to determine whether or not to add parentheses around +// any of the contents. If it’s `null`, no parentheses are added. +function make(name, precedence, contents) { + var e = document.createElementNS(mathml, name); + for (var i = 0; i < contents.length; i++) { + var kid = contents[i]; + var node; + + if (typeof kid === "string") { + node = document.createTextNode(kid); + } else { + // If precedence is non-null and higher than this child’s + // precedence, wrap the child in parentheses. + if (precedence !== null + && (kid.prec < precedence + || (kid.prec == precedence && i != 0))) + { + kid = make("mrow", null, [mo("("), kid, mo(")")]); + } + node = kid.element; + } + e.appendChild(node); + } + if (precedence === null) + precedence = 3; + return {prec: precedence, element: e}; +} + +function convertToMathML(code) { + var mathmlBuilder = { + number: function (s) { return make("mn", 3, [s]); }, + add: function (a, b) { return make("mrow", 1, [a, make("mo", 3, ["+"]), b]); }, + sub: function (a, b) { return make("mrow", 1, [a, make("mo", 3, ["-"]), b]); }, + mul: function (a, b) { return make("mrow", 2, [a, b]); }, + div: function (a, b) { return make("mfrac", null, [a, b]); }, + name: function (name) { return make("mi", 3, [name]); } + }; + var e = parse(code, mathmlBuilder); + return make("math", null, [e]); +} + + +// ## Compilers + +// ### 6. JavaScript function output + +// This is just to show some very basic code generation. +// +// Code generation for a real compiler will be harder, because the target +// language is typically quite a bit different from the source language. Here +// they are virtually identical, so code generation is very easy. +// +function compileToJSFunction(code) { + var jsFunctionBuilder = { + number: function (s) { return s; }, + add: function (a, b) { return "(" + a + " + " + b + ")"; }, + sub: function (a, b) { return "(" + a + " - " + b + ")"; }, + mul: function (a, b) { return "(" + a + " * " + b + ")"; }, + div: function (a, b) { return "(" + a + " / " + b + ")"; }, + name: function (name) { + // Only allow the name "x". + if (name !== "x") + throw SyntaxError("only the name 'x' is allowed"); + return name; + } + }; + + var code = parse(code, jsFunctionBuilder); + return Function("x", "return " + code + ";"); +} + +assert.strictEqual(compileToJSFunction("x*x - 2*x + 1")(1), 0); +assert.strictEqual(compileToJSFunction("x*x - 2*x + 1")(2), 1); +assert.strictEqual(compileToJSFunction("x*x - 2*x + 1")(3), 4); +assert.strictEqual(compileToJSFunction("x*x - 2*x + 1")(4), 9); + + +// ### 7. Complex function output + +// This one returns a JS function that operates on complex numbers. +// +// TODO - explain what is going on here. +// +function compileToComplexFunction(code) { + var nextTmpId = 0; + + function genName() { + return "tmp" + nextTmpId++; + } + + var complexFunctionBuilder = { + number: function (s) { + return { setup: "", re: s, im: "0" }; + }, + add: function (a, b) { + return { + setup: a.setup + b.setup, + re: a.re + " + " + b.re, + im: a.im + " + " + b.im + }; + }, + sub: function (a, b) { + return { + setup: a.setup + b.setup, + re: a.re + " - " + b.re, + im: a.im + " - " + b.im + }; + }, + mul: function (a, b) { + // This requires some setup. First write some code to store the + // real and imaginary parts of a and b in temporary variables. + // We have to store them in temporary variables because the formula + // for complex multiplication uses each component twice, and we + // don’t want to compute them twice. + var atmp = genName(), + btmp = genName(); + var setup = a.setup + b.setup + + ("var A_re = " + a.re + ", A_im = " + a.im + ";\n").replace(/A/g, atmp) + + ("var B_re = " + b.re + ", B_im = " + b.im + ";\n").replace(/B/g, btmp); + + // Now return the setup, along with expressions for computing the + // real and imaginary parts of (a * b). + return { + setup: setup, + re: "A_re * B_re - A_im * B_im".replace(/A/g, atmp).replace(/B/g, btmp), + im: "A_re * B_im + A_im * B_re".replace(/A/g, atmp).replace(/B/g, btmp) + }; + }, + div: function (a, b) { + // Just as for multiplication, first write some code to store the real + // and imaginary parts of a and b in temporary variables. + var atmp = genName(), + btmp = genName(), + tmp = genName(); + var setup = a.setup + b.setup + + ("var A_re = " + a.re + ", A_im = " + a.im + ";\n").replace(/A/g, atmp) + + ("var B_re = " + b.re + ", B_im = " + b.im + ";\n").replace(/B/g, btmp) + + ("var T = B_re * B_re + B_im * B_im;\n").replace(/T/g, tmp).replace(/B/g, btmp); + return { + setup: setup, + re: "(A_re * B_re + A_im * B_im) / T".replace(/A/g, atmp).replace(/B/g, btmp).replace(/T/g, tmp), + im: "(A_im * B_re - A_re * B_im) / T".replace(/A/g, atmp).replace(/B/g, btmp).replace(/T/g, tmp) + }; + }, + name: function (name) { + if (name === "i") + return {setup: "", re: "0", im: "1"}; + if (name !== "z") + throw SyntaxError("undefined variable: " + name); + return { + setup: "", + re: name + "_re", + im: name + "_im" + }; + } + }; + + var result = parse(code, complexFunctionBuilder); + var tmp = genName(); + var code = + result.setup + + "return {re: " + result.re + ", im: " + result.im + "};\n"; + return Function("z_re, z_im", code); + + /* + I had planned to have this generate asm.js code for extra speed, but it's + so fast already that unless I can think of a more computationally + intensive task, there is no need. + result.setup + + "var " + tmp + " = " + result.re + ";\n" + + "z_im = " + result.im + ";\n" + + "z_re = " + tmp + ";\n" + + */ + +} + +// The last bit of code here simply stores all seven back ends in one place +// where other code can get to them. +var parseModes = { + calc: evaluateAsFloat, + fraction: evaluateAsFraction, + blocks: convertToDOM, + json: convertToAST, + mathml: convertToMathML, + graph: compileToJSFunction, + complex: compileToComplexFunction +}; diff --git a/calculator-parser.js b/calculator-parser.js new file mode 100644 index 0000000..4b2e926 --- /dev/null +++ b/calculator-parser.js @@ -0,0 +1,206 @@ +// # calculator-parser.js +// +// a simple calculator language, in three acts + +// This program parses a very simple language that just does a little basic +// arithmetic. Here are some simple examples of the sort of thing you can +// write in the calculator language: +// +// * `2 + 2` +// * `1 * 2 + 3 * 4 + 5 / 6` +// * `3 + 1/(7 + 1/(15 + 1/(1 + 1/(292 + 1/(1 + 1/(1 + 1/1))))))` +// * `1 / ((z + 1) * (z - 1))` +// +// If you’d like to try it out, open [calculator.html](../calculator.html). + + +// ## Act One – Breaking code down into tokens + +// This function, `tokenize(code)`, takes a string `code` and splits it into +// *tokens*, the numbers, words, and symbols that make up our little calculator +// mini-language. +function tokenize(code) { + var results = []; + var tokenRegExp = /\s*([A-Za-z]+|[0-9]+|\S)\s*/g; + + var m; + while ((m = tokenRegExp.exec(code)) !== null) + results.push(m[1]); + return results; +} + +// Let’s test as we go! +var assert = require('assert'); +assert.deepEqual(tokenize("123\n"), ["123"]); +assert.deepEqual(tokenize("2+2"), ["2", "+", "2"]); +assert.deepEqual(tokenize("+-*/"), ["+", "-", "*", "/"]); +assert.deepEqual(tokenize(" 1 * 24 +\n\n pi"), ["1", "*", "24", "+", "pi"]); +assert.deepEqual(tokenize("()"), ["(", ")"]); +assert.deepEqual(tokenize(" "), []); + + + +// Here are a few helper functions for working with tokens. To keep things +// simple, a number is any sequence of digits. +function isNumber(token) { + return token !== undefined && token.match(/^[0-9]+$/) !== null; +} + +// And a *name*, or identifier, is any sequence of letters. +function isName(token) { + return token !== undefined && token.match(/^[A-Za-z]+$/) !== null; +} + +// Tests. +assert(isNumber("123")); +assert(!isNumber("x")); +assert(!isNumber("-")); +assert(isName("xyz")); +assert(!isName("+")); + + +// ## Act Three – Parser output + +// The parser’s only job is to *decode* the input. +// +// *Executing* a program is this object’s job. I’m putting this +// right up front so you can see what the language can actually do, +// before reading on. +// +// The parser will call these six methods as it decodes each +// piece of the input code. +// +var calculator = { + number: function (s) { return parseInt(s); }, + add: function (a, b) { return a + b; }, + sub: function (a, b) { return a - b; }, + mul: function (a, b) { return a * b; }, + div: function (a, b) { return a / b; }, + _variables: Object.create(null), + name: function (name) { return this._variables[name] || 0; } +}; + +calculator._variables.e = Math.E; +calculator._variables.pi = Math.PI; + + + +// ## Act Two – The parser + +// Parse the given string `code` as an expression in our little language. +// +function parse(code, out) { + // Break the input into tokens. + var tokens = tokenize(code); + + // The parser will do a single left-to-right pass over `tokens`, with no + // backtracking. `position` is the index of the next token. Start at + // 0. We’ll increment this as we go. + var position = 0; + + // `peek()` returns the next token without advancing `position`. + function peek() { + return tokens[position]; + } + + // `consume(token)` consumes one token, moving `position` to point to the next one. + function consume(token) { + assert.strictEqual(token, tokens[position]); + position++; + } + + // Now we have the functions that are actually responsible for parsing. + // This is the cool part. Each group of syntax rules is translated to one + // function. + + // Parse a *PrimaryExpr*—that is, tokens matching one of the three syntax + // rules below. Whatever kind of expression we find, we return the result + // of some `out.something()` method. + // + //
+ //
*PrimaryExpr* **:**
+ //
+ //
*Number*
+ //
*Name*
+ //
( *Expr* )
+ //
+ //
+ function parsePrimaryExpr() { + var t = peek(); + + if (isNumber(t)) { + consume(t); + return out.number(t); + } else if (isName(t)) { + consume(t); + return out.name(t); + } else if (t === "(") { + consume(t); + var expr = parseExpr(); + if (peek() !== ")") + throw new SyntaxError("expected )"); + consume(")"); + return expr; + } else { + // If we get here, the next token doesn’t match any of the three + // rules. So it’s an error. + throw new SyntaxError("expected a number, a variable, or parentheses"); + } + } + + //
+ // *MulExpr* **:** + //
+ //
*PrimaryExpr* ( \* *PrimaryExpr* | / *PrimaryExpr* )\*
+ //
+ //
+ function parseMulExpr() { + var expr = parsePrimaryExpr(); + var t = peek(); + while (t === "*" || t === "/") { + consume(t); + var rhs = parsePrimaryExpr(); + if (t === "*") + expr = out.mul(expr, rhs); + else + expr = out.div(expr, rhs); + t = peek(); + } + return expr; + } + + //
+ // *Expr* **:** + //
+ //
*MulExpr* ( + *MulExpr* | - *MulExpr* )\*
+ //
+ //
+ function parseExpr() { + var expr = parseMulExpr(); + var t = peek(); + while (t === "+" || t === "-") { + consume(t); + var rhs = parseMulExpr(); + if (t === "+") + expr = out.add(expr, rhs); + else + expr = out.sub(expr, rhs); + t = peek(); + } + return expr; + } + + // Now all that remains, really, is to call `parseExpr()` to parse an *Expr*. + var result = parseExpr(); + + // Well, one more thing. Make sure `parseExpr()` consumed *all* the + // input. If it didn’t, that means the next token didn’t match any syntax + // rule, which is an error. + if (position !== tokens.length) + throw new SyntaxError("unexpected '" + peek() + "'"); + return result; +} + +assert.strictEqual(parse("2 + 2", calculator), 4); +assert.strictEqual(parse("3 * 4 * 5", calculator), 60); +assert.strictEqual(parse("5 * (2 + 2)", calculator), 20); diff --git a/calculator.html b/calculator.html new file mode 100644 index 0000000..8335e87 --- /dev/null +++ b/calculator.html @@ -0,0 +1,78 @@ + + + + + Compilers 101 - A toy calculator + + + + + + + + + + + + +
+

Compilers 101 Toy Calculator

+ + +

+    
+
+
+
+
+ + diff --git a/third-party-scripts/Chart.js b/third-party-scripts/Chart.js new file mode 100644 index 0000000..7024c94 --- /dev/null +++ b/third-party-scripts/Chart.js @@ -0,0 +1,1426 @@ +//Define the global Chart Variable as a class. +var Chart = function(context){ + + var chart = this; + + + //Easing functions adapted from Robert Penner's easing equations + //http://www.robertpenner.com/easing/ + + var animationOptions = { + linear : function (t){ + return t; + }, + easeInQuad: function (t) { + return t*t; + }, + easeOutQuad: function (t) { + return -1 *t*(t-2); + }, + easeInOutQuad: function (t) { + if ((t/=1/2) < 1) return 1/2*t*t; + return -1/2 * ((--t)*(t-2) - 1); + }, + easeInCubic: function (t) { + return t*t*t; + }, + easeOutCubic: function (t) { + return 1*((t=t/1-1)*t*t + 1); + }, + easeInOutCubic: function (t) { + if ((t/=1/2) < 1) return 1/2*t*t*t; + return 1/2*((t-=2)*t*t + 2); + }, + easeInQuart: function (t) { + return t*t*t*t; + }, + easeOutQuart: function (t) { + return -1 * ((t=t/1-1)*t*t*t - 1); + }, + easeInOutQuart: function (t) { + if ((t/=1/2) < 1) return 1/2*t*t*t*t; + return -1/2 * ((t-=2)*t*t*t - 2); + }, + easeInQuint: function (t) { + return 1*(t/=1)*t*t*t*t; + }, + easeOutQuint: function (t) { + return 1*((t=t/1-1)*t*t*t*t + 1); + }, + easeInOutQuint: function (t) { + if ((t/=1/2) < 1) return 1/2*t*t*t*t*t; + return 1/2*((t-=2)*t*t*t*t + 2); + }, + easeInSine: function (t) { + return -1 * Math.cos(t/1 * (Math.PI/2)) + 1; + }, + easeOutSine: function (t) { + return 1 * Math.sin(t/1 * (Math.PI/2)); + }, + easeInOutSine: function (t) { + return -1/2 * (Math.cos(Math.PI*t/1) - 1); + }, + easeInExpo: function (t) { + return (t==0) ? 1 : 1 * Math.pow(2, 10 * (t/1 - 1)); + }, + easeOutExpo: function (t) { + return (t==1) ? 1 : 1 * (-Math.pow(2, -10 * t/1) + 1); + }, + easeInOutExpo: function (t) { + if (t==0) return 0; + if (t==1) return 1; + if ((t/=1/2) < 1) return 1/2 * Math.pow(2, 10 * (t - 1)); + return 1/2 * (-Math.pow(2, -10 * --t) + 2); + }, + easeInCirc: function (t) { + if (t>=1) return t; + return -1 * (Math.sqrt(1 - (t/=1)*t) - 1); + }, + easeOutCirc: function (t) { + return 1 * Math.sqrt(1 - (t=t/1-1)*t); + }, + easeInOutCirc: function (t) { + if ((t/=1/2) < 1) return -1/2 * (Math.sqrt(1 - t*t) - 1); + return 1/2 * (Math.sqrt(1 - (t-=2)*t) + 1); + }, + easeInElastic: function (t) { + var s=1.70158;var p=0;var a=1; + if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3; + if (a < Math.abs(1)) { a=1; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (1/a); + return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )); + }, + easeOutElastic: function (t) { + var s=1.70158;var p=0;var a=1; + if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3; + if (a < Math.abs(1)) { a=1; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (1/a); + return a*Math.pow(2,-10*t) * Math.sin( (t*1-s)*(2*Math.PI)/p ) + 1; + }, + easeInOutElastic: function (t) { + var s=1.70158;var p=0;var a=1; + if (t==0) return 0; if ((t/=1/2)==2) return 1; if (!p) p=1*(.3*1.5); + if (a < Math.abs(1)) { a=1; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (1/a); + if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )); + return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )*.5 + 1; + }, + easeInBack: function (t) { + var s = 1.70158; + return 1*(t/=1)*t*((s+1)*t - s); + }, + easeOutBack: function (t) { + var s = 1.70158; + return 1*((t=t/1-1)*t*((s+1)*t + s) + 1); + }, + easeInOutBack: function (t) { + var s = 1.70158; + if ((t/=1/2) < 1) return 1/2*(t*t*(((s*=(1.525))+1)*t - s)); + return 1/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2); + }, + easeInBounce: function (t) { + return 1 - animationOptions.easeOutBounce (1-t); + }, + easeOutBounce: function (t) { + if ((t/=1) < (1/2.75)) { + return 1*(7.5625*t*t); + } else if (t < (2/2.75)) { + return 1*(7.5625*(t-=(1.5/2.75))*t + .75); + } else if (t < (2.5/2.75)) { + return 1*(7.5625*(t-=(2.25/2.75))*t + .9375); + } else { + return 1*(7.5625*(t-=(2.625/2.75))*t + .984375); + } + }, + easeInOutBounce: function (t) { + if (t < 1/2) return animationOptions.easeInBounce (t*2) * .5; + return animationOptions.easeOutBounce (t*2-1) * .5 + 1*.5; + } + }; + + //Variables global to the chart + var width = context.canvas.width; + var height = context.canvas.height; + + + //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. + if (window.devicePixelRatio) { + context.canvas.style.width = width + "px"; + context.canvas.style.height = height + "px"; + context.canvas.height = height * window.devicePixelRatio; + context.canvas.width = width * window.devicePixelRatio; + context.scale(window.devicePixelRatio, window.devicePixelRatio); + } + + this.PolarArea = function(data,options){ + + chart.PolarArea.defaults = { + scaleOverlay : true, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleShowLine : true, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : true, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowLabelBackdrop : true, + scaleBackdropColor : "rgba(255,255,255,0.75)", + scaleBackdropPaddingY : 2, + scaleBackdropPaddingX : 2, + segmentShowStroke : true, + segmentStrokeColor : "#fff", + segmentStrokeWidth : 2, + animation : true, + animationSteps : 100, + animationEasing : "easeOutBounce", + animateRotate : true, + animateScale : false, + onAnimationComplete : null + }; + + var config = (options)? mergeChartConfig(chart.PolarArea.defaults,options) : chart.PolarArea.defaults; + + return new PolarArea(data,config,context); + }; + + this.Radar = function(data,options){ + + chart.Radar.defaults = { + scaleOverlay : false, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleShowLine : true, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : false, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowLabelBackdrop : true, + scaleBackdropColor : "rgba(255,255,255,0.75)", + scaleBackdropPaddingY : 2, + scaleBackdropPaddingX : 2, + angleShowLineOut : true, + angleLineColor : "rgba(0,0,0,.1)", + angleLineWidth : 1, + pointLabelFontFamily : "'Arial'", + pointLabelFontStyle : "normal", + pointLabelFontSize : 12, + pointLabelFontColor : "#666", + pointDot : true, + pointDotRadius : 3, + pointDotStrokeWidth : 1, + datasetStroke : true, + datasetStrokeWidth : 2, + datasetFill : true, + animation : true, + animationSteps : 60, + animationEasing : "easeOutQuart", + onAnimationComplete : null + }; + + var config = (options)? mergeChartConfig(chart.Radar.defaults,options) : chart.Radar.defaults; + + return new Radar(data,config,context); + }; + + this.Pie = function(data,options){ + chart.Pie.defaults = { + segmentShowStroke : true, + segmentStrokeColor : "#fff", + segmentStrokeWidth : 2, + animation : true, + animationSteps : 100, + animationEasing : "easeOutBounce", + animateRotate : true, + animateScale : false, + onAnimationComplete : null + }; + + var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults; + + return new Pie(data,config,context); + }; + + this.Doughnut = function(data,options){ + chart.Doughnut.defaults = { + segmentShowStroke : true, + segmentStrokeColor : "#fff", + segmentStrokeWidth : 2, + percentageInnerCutout : 50, + animation : true, + animationSteps : 100, + animationEasing : "easeOutBounce", + animateRotate : true, + animateScale : false, + onAnimationComplete : null + }; + + var config = (options)? mergeChartConfig(chart.Doughnut.defaults,options) : chart.Doughnut.defaults; + + return new Doughnut(data,config,context); + + }; + + this.Line = function(data,options){ + + chart.Line.defaults = { + scaleOverlay : false, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : true, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowGridLines : true, + scaleGridLineColor : "rgba(0,0,0,.05)", + scaleGridLineWidth : 1, + bezierCurve : true, + pointDot : true, + pointDotRadius : 4, + pointDotStrokeWidth : 2, + datasetStroke : true, + datasetStrokeWidth : 2, + datasetFill : true, + animation : true, + animationSteps : 60, + animationEasing : "easeOutQuart", + onAnimationComplete : null + }; + var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults; + + return new Line(data,config,context); + } + + this.Bar = function(data,options){ + chart.Bar.defaults = { + scaleOverlay : false, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : true, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowGridLines : true, + scaleGridLineColor : "rgba(0,0,0,.05)", + scaleGridLineWidth : 1, + barShowStroke : true, + barStrokeWidth : 2, + barValueSpacing : 5, + barDatasetSpacing : 1, + animation : true, + animationSteps : 60, + animationEasing : "easeOutQuart", + onAnimationComplete : null + }; + var config = (options) ? mergeChartConfig(chart.Bar.defaults,options) : chart.Bar.defaults; + + return new Bar(data,config,context); + } + + var clear = function(c){ + c.clearRect(0, 0, width, height); + }; + + var PolarArea = function(data,config,ctx){ + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; + + + calculateDrawingSizes(); + + valueBounds = getValueBounds(); + + labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; + + //Check and set the scale + if (!config.scaleOverride){ + + calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); + } + else { + calculatedScale = { + steps : config.scaleSteps, + stepValue : config.scaleStepWidth, + graphMin : config.scaleStartValue, + labels : [] + } + for (var i=0; i upperValue) {upperValue = data[i].value;} + if (data[i].value < lowerValue) {lowerValue = data[i].value;} + }; + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + + + } + } + + var Radar = function (data,config,ctx) { + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; + + //If no labels are defined set to an empty array, so referencing length for looping doesn't blow up. + if (!data.labels) data.labels = []; + + calculateDrawingSizes(); + + var valueBounds = getValueBounds(); + + labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; + + //Check and set the scale + if (!config.scaleOverride){ + + calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); + } + else { + calculatedScale = { + steps : config.scaleSteps, + stepValue : config.scaleStepWidth, + graphMin : config.scaleStartValue, + labels : [] + } + for (var i=0; i Math.PI){ + ctx.textAlign = "right"; + } + else{ + ctx.textAlign = "left"; + } + + ctx.textBaseline = "middle"; + + ctx.fillText(data.labels[k],opposite,-adjacent); + + } + ctx.restore(); + }; + function calculateDrawingSizes(){ + maxSize = (Min([width,height])/2); + + labelHeight = config.scaleFontSize*2; + + var labelLength = 0; + for (var i=0; ilabelLength) labelLength = textMeasurement; + } + + //Figure out whats the largest - the height of the text or the width of what's there, and minus it from the maximum usable size. + maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]); + + maxSize -= config.pointLabelFontSize; + maxSize = CapValue(maxSize, null, 0); + scaleHeight = maxSize; + //If the label height is less than 5, set it to 5 so we don't have lines on top of each other. + labelHeight = Default(labelHeight,5); + }; + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + + for (var i=0; i upperValue){upperValue = data.datasets[i].data[j]} + if (data.datasets[i].data[j] < lowerValue){lowerValue = data.datasets[i].data[j]} + } + } + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + + + } + } + + var Pie = function(data,config,ctx){ + var segmentTotal = 0; + + //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge. + var pieRadius = Min([height/2,width/2]) - 5; + + for (var i=0; i 0){ + ctx.save(); + ctx.textAlign = "right"; + } + else{ + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + for (var i=0; i 0){ + ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); + ctx.rotate(-(rotateLabels * (Math.PI/180))); + ctx.fillText(data.labels[i], 0,0); + ctx.restore(); + } + + else{ + ctx.fillText(data.labels[i], yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize+3); + } + + ctx.beginPath(); + ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY+3); + + //Check i isnt 0, so we dont go over the Y axis twice. + if(config.scaleShowGridLines && i>0){ + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + ctx.lineTo(yAxisPosX + i * valueHop, 5); + } + else{ + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY+3); + } + ctx.stroke(); + } + + //Y axis + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX,xAxisPosY+5); + ctx.lineTo(yAxisPosX,5); + ctx.stroke(); + + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j=0; j longestText)? measuredText : longestText; + } + //Add a little extra padding from the y axis + longestText +=10; + } + xAxisLength = width - longestText - widestXLabel; + valueHop = Math.floor(xAxisLength/(data.labels.length-1)); + + yAxisPosX = width-widestXLabel/2-xAxisLength; + xAxisPosY = scaleHeight + config.scaleFontSize/2; + } + function calculateDrawingSizes(){ + maxSize = height; + + //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees. + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; + widestXLabel = 1; + for (var i=0; i widestXLabel)? textLength : widestXLabel; + } + if (width/data.labels.length < widestXLabel){ + rotateLabels = 45; + if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){ + rotateLabels = 90; + maxSize -= widestXLabel; + } + else{ + maxSize -= Math.sin(rotateLabels) * widestXLabel; + } + } + else{ + maxSize -= config.scaleFontSize; + } + + //Add a little padding between the x line and the text + maxSize -= 5; + + + labelHeight = config.scaleFontSize; + + maxSize -= labelHeight; + //Set 5 pixels greater than the font size to allow for a little padding from the X axis. + + scaleHeight = maxSize; + + //Then get the area above we can safely draw on. + + } + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + for (var i=0; i upperValue) { upperValue = data.datasets[i].data[j] }; + if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; + } + }; + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + + + } + + + } + + var Bar = function(data,config,ctx){ + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY,barWidth, rotateLabels = 0; + + calculateDrawingSizes(); + + valueBounds = getValueBounds(); + //Check and set the scale + labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : ""; + if (!config.scaleOverride){ + + calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); + } + else { + calculatedScale = { + steps : config.scaleSteps, + stepValue : config.scaleStepWidth, + graphMin : config.scaleStartValue, + labels : [] + } + for (var i=0; i 0){ + ctx.save(); + ctx.textAlign = "right"; + } + else{ + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + for (var i=0; i 0){ + ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); + ctx.rotate(-(rotateLabels * (Math.PI/180))); + ctx.fillText(data.labels[i], 0,0); + ctx.restore(); + } + + else{ + ctx.fillText(data.labels[i], yAxisPosX + i*valueHop + valueHop/2,xAxisPosY + config.scaleFontSize+3); + } + + ctx.beginPath(); + ctx.moveTo(yAxisPosX + (i+1) * valueHop, xAxisPosY+3); + + //Check i isnt 0, so we dont go over the Y axis twice. + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + ctx.lineTo(yAxisPosX + (i+1) * valueHop, 5); + ctx.stroke(); + } + + //Y axis + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX,xAxisPosY+5); + ctx.lineTo(yAxisPosX,5); + ctx.stroke(); + + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j=0; j longestText)? measuredText : longestText; + } + //Add a little extra padding from the y axis + longestText +=10; + } + xAxisLength = width - longestText - widestXLabel; + valueHop = Math.floor(xAxisLength/(data.labels.length)); + + barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length; + + yAxisPosX = width-widestXLabel/2-xAxisLength; + xAxisPosY = scaleHeight + config.scaleFontSize/2; + } + function calculateDrawingSizes(){ + maxSize = height; + + //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees. + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; + widestXLabel = 1; + for (var i=0; i widestXLabel)? textLength : widestXLabel; + } + if (width/data.labels.length < widestXLabel){ + rotateLabels = 45; + if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){ + rotateLabels = 90; + maxSize -= widestXLabel; + } + else{ + maxSize -= Math.sin(rotateLabels) * widestXLabel; + } + } + else{ + maxSize -= config.scaleFontSize; + } + + //Add a little padding between the x line and the text + maxSize -= 5; + + + labelHeight = config.scaleFontSize; + + maxSize -= labelHeight; + //Set 5 pixels greater than the font size to allow for a little padding from the X axis. + + scaleHeight = maxSize; + + //Then get the area above we can safely draw on. + + } + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + for (var i=0; i upperValue) { upperValue = data.datasets[i].data[j] }; + if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; + } + }; + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + + + } + } + + function calculateOffset(val,calculatedScale,scaleHop){ + var outerValue = calculatedScale.steps * calculatedScale.stepValue; + var adjustedValue = val - calculatedScale.graphMin; + var scalingFactor = CapValue(adjustedValue/outerValue,1,0); + return (scaleHop*calculatedScale.steps) * scalingFactor; + } + + function animationLoop(config,drawScale,drawData,ctx){ + var animFrameAmount = (config.animation)? 1/CapValue(config.animationSteps,Number.MAX_VALUE,1) : 1, + easingFunction = animationOptions[config.animationEasing], + percentAnimComplete =(config.animation)? 0 : 1; + + + + if (typeof drawScale !== "function") drawScale = function(){}; + + requestAnimFrame(animLoop); + + function animateFrame(){ + var easeAdjustedAnimationPercent =(config.animation)? CapValue(easingFunction(percentAnimComplete),null,0) : 1; + clear(ctx); + if(config.scaleOverlay){ + drawData(easeAdjustedAnimationPercent); + drawScale(); + } else { + drawScale(); + drawData(easeAdjustedAnimationPercent); + } + } + function animLoop(){ + //We need to check if the animation is incomplete (less than 1), or complete (1). + percentAnimComplete += animFrameAmount; + animateFrame(); + //Stop the loop continuing forever + if (percentAnimComplete <= 1){ + requestAnimFrame(animLoop); + } + else{ + if (typeof config.onAnimationComplete == "function") config.onAnimationComplete(); + } + + } + + } + + //Declare global functions to be called within this namespace here. + // shim layer with setTimeout fallback + var requestAnimFrame = (function(){ + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + window.setTimeout(callback, 1000 / 60); + }; + })(); + + function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){ + var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum; + + valueRange = maxValue - minValue; + + rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange); + //var graphRange = (Math.ceil(rangeMultipland) * Math.pow(10, rangeOrderOfMagnitude)); + graphMin = (Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude)); + + graphMax = (Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude)); + + graphRange = graphMax - graphMin; + + stepValue = Math.pow(10, rangeOrderOfMagnitude); + numberOfSteps = Math.round(graphRange / stepValue); + + + while(numberOfSteps < minSteps || numberOfSteps > maxSteps) { + if (numberOfSteps < minSteps){ + stepValue /= 2; + numberOfSteps = Math.round(graphRange/stepValue); + } + else{ + stepValue *=2; + numberOfSteps = Math.round(graphRange/stepValue); + } + }; + + //Compare number of steps to the max and min for that size graph, and add in half steps if need be. + + //Create an array of all the labels by interpolating the string. + + var labels = []; + if(labelTemplateString){ + //Fix floating point errors by setting to fixed the on the same decimal as the stepValue. + for (var i=1; i maxValue ) { + return maxValue; + } + } + if(isNumber(minValue)){ + if ( valueToCap < minValue ){ + return minValue; + } + } + return valueToCap; + } + function getDecimalPlaces (num){ + var numberOfDecimalPlaces; + if (num%1!=0){ + return num.toString().split(".")[1].length + } + else{ + return 0; + } + + } + function mergeChartConfig(defaults,userDefined){ + var returnObj = {}; + for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; } + for (var attrname in userDefined) { returnObj[attrname] = userDefined[attrname]; } + return returnObj; + } + //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/ + var cache = {}; + + function tmpl(str, data){ + // Figure out if we're getting a template, or if we need to + // load the template - and be sure to cache the result. + var fn = !/\W/.test(str) ? + cache[str] = cache[str] || + tmpl(document.getElementById(str).innerHTML) : + + // Generate a reusable function that will serve as a template + // generator (and which will be cached). + new Function("obj", + "var p=[],print=function(){p.push.apply(p,arguments);};" + + + // Introduce the data as local variables using with(){} + "with(obj){p.push('" + + + // Convert the template into pure JavaScript + str + .replace(/[\r\t\n]/g, " ") + .split("<%").join("\t") + .replace(/((^|%>)[^\t]*)'/g, "$1\r") + .replace(/\t=(.*?)%>/g, "',$1,'") + .split("\t").join("');") + .split("%>").join("p.push('") + .split("\r").join("\\'") + + "');}return p.join('');"); + + // Provide some basic currying to the user + return data ? fn( data ) : fn; + }; +} + + + diff --git a/third-party-scripts/assert.js b/third-party-scripts/assert.js new file mode 100644 index 0000000..7db7b15 --- /dev/null +++ b/third-party-scripts/assert.js @@ -0,0 +1,297 @@ +var assert = function () { return assert.ok.apply(this, arguments); }; +(function (exports) { + "use strict"; + +// http://wiki.commonjs.org/wiki/Unit_Testing/1.0 +// +// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! +// +// Originally from narwhal.js (http://narwhaljs.org) +// Copyright (c) 2009 Thomas Robinson <280north.com> +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the 'Software'), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +var pSlice = Array.prototype.slice; + +// 1. The assert module provides functions that throw +// AssertionError's when particular conditions are not met. The +// assert module must conform to the following interface. + +var assert = exports; + +// 2. The AssertionError is defined in assert. +// new assert.AssertionError({ message: message, +// actual: actual, +// expected: expected }) + +assert.AssertionError = function AssertionError(options) { + this.name = 'AssertionError'; + this.message = options.message; + this.actual = options.actual; + this.expected = options.expected; + this.operator = options.operator; + var stackStartFunction = options.stackStartFunction || fail; + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, stackStartFunction); + } +}; +assert.AssertionError.prototype = Object.create(Error.prototype); + +assert.AssertionError.prototype.toString = function() { + if (this.message) { + return [this.name + ':', this.message].join(' '); + } else { + return [this.name + ':', + JSON.stringify(this.expected), + this.operator, + JSON.stringify(this.actual)].join(' '); + } +}; + +// assert.AssertionError instanceof Error + +assert.AssertionError.__proto__ = Error.prototype; + +// At present only the three keys mentioned above are used and +// understood by the spec. Implementations or sub modules can pass +// other keys to the AssertionError's constructor - they will be +// ignored. + +// 3. All of the following functions must throw an AssertionError +// when a corresponding condition is not met, with a message that +// may be undefined if not provided. All assertion methods provide +// both the actual and expected values to the assertion error for +// display purposes. + +function fail(actual, expected, message, operator, stackStartFunction) { + throw new assert.AssertionError({ + message: message, + actual: actual, + expected: expected, + operator: operator, + stackStartFunction: stackStartFunction + }); +} + +// EXTENSION! allows for well behaved errors defined elsewhere. +assert.fail = fail; + +// 4. Pure assertion tests whether a value is truthy, as determined +// by !!guard. +// assert.ok(guard, message_opt); +// This statement is equivalent to assert.equal(true, guard, +// message_opt);. To test strictly for the value true, use +// assert.strictEqual(true, guard, message_opt);. + +assert.ok = function ok(value, message) { + if (!!!value) fail(value, true, message, '==', assert.ok); +}; + +// 5. The equality assertion tests shallow, coercive equality with +// ==. +// assert.equal(actual, expected, message_opt); + +assert.equal = function equal(actual, expected, message) { + if (actual != expected) fail(actual, expected, message, '==', assert.equal); +}; + +// 6. The non-equality assertion tests for whether two objects are not equal +// with != assert.notEqual(actual, expected, message_opt); + +assert.notEqual = function notEqual(actual, expected, message) { + if (actual == expected) { + fail(actual, expected, message, '!=', assert.notEqual); + } +}; + +// 7. The equivalence assertion tests a deep equality relation. +// assert.deepEqual(actual, expected, message_opt); + +assert.deepEqual = function deepEqual(actual, expected, message) { + if (!_deepEqual(actual, expected)) { + fail(actual, expected, message, 'deepEqual', assert.deepEqual); + } +}; + +function _deepEqual(actual, expected) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (actual instanceof Date && expected instanceof Date) { + return actual.getTime() === expected.getTime(); + + // 7.3. Other pairs that do not both pass typeof value == 'object', + // equivalence is determined by ==. + } else if (typeof actual != 'object' && typeof expected != 'object') { + return actual == expected; + + // 7.4. For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical 'prototype' property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected); + } +} + +function isUndefinedOrNull(value) { + return value === null || value === undefined; +} + +function isArguments(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +function objEquiv(a, b) { + if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) + return false; + // an identical 'prototype' property. + if (a.prototype !== b.prototype) return false; + //~~~I've managed to break Object.keys through screwy arguments passing. + // Converting to array solves the problem. + if (isArguments(a)) { + if (!isArguments(b)) { + return false; + } + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a, b); + } + try { + var ka = Object.keys(a), + kb = Object.keys(b), + key, i; + } catch (e) {//happens when one is a string literal and the other isn't + return false; + } + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length != kb.length) + return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!_deepEqual(a[key], b[key])) return false; + } + return true; +} + +// 8. The non-equivalence assertion tests for any deep inequality. +// assert.notDeepEqual(actual, expected, message_opt); + +assert.notDeepEqual = function notDeepEqual(actual, expected, message) { + if (_deepEqual(actual, expected)) { + fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); + } +}; + +// 9. The strict equality assertion tests strict equality, as determined by ===. +// assert.strictEqual(actual, expected, message_opt); + +assert.strictEqual = function strictEqual(actual, expected, message) { + if (actual !== expected) { + fail(actual, expected, message, '===', assert.strictEqual); + } +}; + +// 10. The strict non-equality assertion tests for strict inequality, as +// determined by !==. assert.notStrictEqual(actual, expected, message_opt); + +assert.notStrictEqual = function notStrictEqual(actual, expected, message) { + if (actual === expected) { + fail(actual, expected, message, '!==', assert.notStrictEqual); + } +}; + +function expectedException(actual, expected) { + if (!actual || !expected) { + return false; + } + + if (expected instanceof RegExp) { + return expected.test(actual); + } else if (actual instanceof expected) { + return true; + } else if (expected.call({}, actual) === true) { + return true; + } + + return false; +} + +function _throws(shouldThrow, block, expected, message) { + var actual; + + if (typeof expected === 'string') { + message = expected; + expected = null; + } + + try { + block(); + } catch (e) { + actual = e; + } + + message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + + (message ? ' ' + message : '.'); + + if (shouldThrow && !actual) { + fail('Missing expected exception' + message); + } + + if (!shouldThrow && expectedException(actual, expected)) { + fail('Got unwanted exception' + message); + } + + if ((shouldThrow && actual && expected && + !expectedException(actual, expected)) || (!shouldThrow && actual)) { + throw actual; + } +} + +// 11. Expected to throw an error: +// assert.throws(block, Error_opt, message_opt); + +assert.throws = function(block, /*optional*/error, /*optional*/message) { + _throws.apply(this, [true].concat(pSlice.call(arguments))); +}; + +// EXTENSION! This is annoying to write outside this module. +assert.doesNotThrow = function(block, /*optional*/error, /*optional*/message) { + _throws.apply(this, [false].concat(pSlice.call(arguments))); +}; + +assert.ifError = function(err) { if (err) {throw err;}}; + +}(assert)); diff --git a/third-party-scripts/biginteger.js b/third-party-scripts/biginteger.js new file mode 100644 index 0000000..50a29a5 --- /dev/null +++ b/third-party-scripts/biginteger.js @@ -0,0 +1,1620 @@ +/* + JavaScript BigInteger library version 0.9 + http://silentmatt.com/biginteger/ + + Copyright (c) 2009 Matthew Crumley + Copyright (c) 2010,2011 by John Tobey + Licensed under the MIT license. + + Support for arbitrary internal representation base was added by + Vitaly Magerya. +*/ + +/* + File: biginteger.js + + Exports: + + +*/ +var biginteger = {}; +(function(exports) { +"use strict"; +/* + Class: BigInteger + An arbitrarily-large integer. + + objects should be considered immutable. None of the "built-in" + methods modify *this* or their arguments. All properties should be + considered private. + + All the methods of instances can be called "statically". The + static versions are convenient if you don't already have a + object. + + As an example, these calls are equivalent. + + > BigInteger(4).multiply(5); // returns BigInteger(20); + > BigInteger.multiply(4, 5); // returns BigInteger(20); + + > var a = 42; + > var a = BigInteger.toJSValue("0b101010"); // Not completely useless... +*/ + +var CONSTRUCT = {}; // Unique token to call "private" version of constructor + +/* + Constructor: BigInteger() + Convert a value to a . + + Although is the constructor for objects, it is + best not to call it as a constructor. If *n* is a object, it is + simply returned as-is. Otherwise, is equivalent to + without a radix argument. + + > var n0 = BigInteger(); // Same as + > var n1 = BigInteger("123"); // Create a new with value 123 + > var n2 = BigInteger(123); // Create a new with value 123 + > var n3 = BigInteger(n2); // Return n2, unchanged + + The constructor form only takes an array and a sign. *n* must be an + array of numbers in little-endian order, where each digit is between 0 + and BigInteger.base. The second parameter sets the sign: -1 for + negative, +1 for positive, or 0 for zero. The array is *not copied and + may be modified*. If the array contains only zeros, the sign parameter + is ignored and is forced to zero. + + > new BigInteger([5], -1): create a new BigInteger with value -5 + + Parameters: + + n - Value to convert to a . + + Returns: + + A value. + + See Also: + + , +*/ +function BigInteger(n, s, token) { + if (token !== CONSTRUCT) { + if (n instanceof BigInteger) { + return n; + } + else if (typeof n === "undefined") { + return ZERO; + } + return BigInteger.parse(n); + } + + n = n || []; // Provide the nullary constructor for subclasses. + while (n.length && !n[n.length - 1]) { + --n.length; + } + this._d = n; + this._s = n.length ? (s || 1) : 0; +} + +BigInteger._construct = function(n, s) { + return new BigInteger(n, s, CONSTRUCT); +}; + +// Base-10 speedup hacks in parse, toString, exp10 and log functions +// require base to be a power of 10. 10^7 is the largest such power +// that won't cause a precision loss when digits are multiplied. +var BigInteger_base = 10000000; +var BigInteger_base_log10 = 7; + +BigInteger.base = BigInteger_base; +BigInteger.base_log10 = BigInteger_base_log10; + +var ZERO = new BigInteger([], 0, CONSTRUCT); +// Constant: ZERO +// 0. +BigInteger.ZERO = ZERO; + +var ONE = new BigInteger([1], 1, CONSTRUCT); +// Constant: ONE +// 1. +BigInteger.ONE = ONE; + +var M_ONE = new BigInteger(ONE._d, -1, CONSTRUCT); +// Constant: M_ONE +// -1. +BigInteger.M_ONE = M_ONE; + +// Constant: _0 +// Shortcut for . +BigInteger._0 = ZERO; + +// Constant: _1 +// Shortcut for . +BigInteger._1 = ONE; + +/* + Constant: small + Array of from 0 to 36. + + These are used internally for parsing, but useful when you need a "small" + . + + See Also: + + , , <_0>, <_1> +*/ +BigInteger.small = [ + ZERO, + ONE, + /* Assuming BigInteger_base > 36 */ + new BigInteger( [2], 1, CONSTRUCT), + new BigInteger( [3], 1, CONSTRUCT), + new BigInteger( [4], 1, CONSTRUCT), + new BigInteger( [5], 1, CONSTRUCT), + new BigInteger( [6], 1, CONSTRUCT), + new BigInteger( [7], 1, CONSTRUCT), + new BigInteger( [8], 1, CONSTRUCT), + new BigInteger( [9], 1, CONSTRUCT), + new BigInteger([10], 1, CONSTRUCT), + new BigInteger([11], 1, CONSTRUCT), + new BigInteger([12], 1, CONSTRUCT), + new BigInteger([13], 1, CONSTRUCT), + new BigInteger([14], 1, CONSTRUCT), + new BigInteger([15], 1, CONSTRUCT), + new BigInteger([16], 1, CONSTRUCT), + new BigInteger([17], 1, CONSTRUCT), + new BigInteger([18], 1, CONSTRUCT), + new BigInteger([19], 1, CONSTRUCT), + new BigInteger([20], 1, CONSTRUCT), + new BigInteger([21], 1, CONSTRUCT), + new BigInteger([22], 1, CONSTRUCT), + new BigInteger([23], 1, CONSTRUCT), + new BigInteger([24], 1, CONSTRUCT), + new BigInteger([25], 1, CONSTRUCT), + new BigInteger([26], 1, CONSTRUCT), + new BigInteger([27], 1, CONSTRUCT), + new BigInteger([28], 1, CONSTRUCT), + new BigInteger([29], 1, CONSTRUCT), + new BigInteger([30], 1, CONSTRUCT), + new BigInteger([31], 1, CONSTRUCT), + new BigInteger([32], 1, CONSTRUCT), + new BigInteger([33], 1, CONSTRUCT), + new BigInteger([34], 1, CONSTRUCT), + new BigInteger([35], 1, CONSTRUCT), + new BigInteger([36], 1, CONSTRUCT) +]; + +// Used for parsing/radix conversion +BigInteger.digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""); + +/* + Method: toString + Convert a to a string. + + When *base* is greater than 10, letters are upper case. + + Parameters: + + base - Optional base to represent the number in (default is base 10). + Must be between 2 and 36 inclusive, or an Error will be thrown. + + Returns: + + The string representation of the . +*/ +BigInteger.prototype.toString = function(base) { + base = +base || 10; + if (base < 2 || base > 36) { + throw new Error("illegal radix " + base + "."); + } + if (this._s === 0) { + return "0"; + } + if (base === 10) { + var str = this._s < 0 ? "-" : ""; + str += this._d[this._d.length - 1].toString(); + for (var i = this._d.length - 2; i >= 0; i--) { + var group = this._d[i].toString(); + while (group.length < BigInteger_base_log10) group = '0' + group; + str += group; + } + return str; + } + else { + var numerals = BigInteger.digits; + base = BigInteger.small[base]; + var sign = this._s; + + var n = this.abs(); + var digits = []; + var digit; + + while (n._s !== 0) { + var divmod = n.divRem(base); + n = divmod[0]; + digit = divmod[1]; + // TODO: This could be changed to unshift instead of reversing at the end. + // Benchmark both to compare speeds. + digits.push(numerals[digit.valueOf()]); + } + return (sign < 0 ? "-" : "") + digits.reverse().join(""); + } +}; + +// Verify strings for parsing +BigInteger.radixRegex = [ + /^$/, + /^$/, + /^[01]*$/, + /^[012]*$/, + /^[0-3]*$/, + /^[0-4]*$/, + /^[0-5]*$/, + /^[0-6]*$/, + /^[0-7]*$/, + /^[0-8]*$/, + /^[0-9]*$/, + /^[0-9aA]*$/, + /^[0-9abAB]*$/, + /^[0-9abcABC]*$/, + /^[0-9a-dA-D]*$/, + /^[0-9a-eA-E]*$/, + /^[0-9a-fA-F]*$/, + /^[0-9a-gA-G]*$/, + /^[0-9a-hA-H]*$/, + /^[0-9a-iA-I]*$/, + /^[0-9a-jA-J]*$/, + /^[0-9a-kA-K]*$/, + /^[0-9a-lA-L]*$/, + /^[0-9a-mA-M]*$/, + /^[0-9a-nA-N]*$/, + /^[0-9a-oA-O]*$/, + /^[0-9a-pA-P]*$/, + /^[0-9a-qA-Q]*$/, + /^[0-9a-rA-R]*$/, + /^[0-9a-sA-S]*$/, + /^[0-9a-tA-T]*$/, + /^[0-9a-uA-U]*$/, + /^[0-9a-vA-V]*$/, + /^[0-9a-wA-W]*$/, + /^[0-9a-xA-X]*$/, + /^[0-9a-yA-Y]*$/, + /^[0-9a-zA-Z]*$/ +]; + +/* + Function: parse + Parse a string into a . + + *base* is optional but, if provided, must be from 2 to 36 inclusive. If + *base* is not provided, it will be guessed based on the leading characters + of *s* as follows: + + - "0x" or "0X": *base* = 16 + - "0c" or "0C": *base* = 8 + - "0b" or "0B": *base* = 2 + - else: *base* = 10 + + If no base is provided, or *base* is 10, the number can be in exponential + form. For example, these are all valid: + + > BigInteger.parse("1e9"); // Same as "1000000000" + > BigInteger.parse("1.234*10^3"); // Same as 1234 + > BigInteger.parse("56789 * 10 ** -2"); // Same as 567 + + If any characters fall outside the range defined by the radix, an exception + will be thrown. + + Parameters: + + s - The string to parse. + base - Optional radix (default is to guess based on *s*). + + Returns: + + a instance. +*/ +BigInteger.parse = function(s, base) { + // Expands a number in exponential form to decimal form. + // expandExponential("-13.441*10^5") === "1344100"; + // expandExponential("1.12300e-1") === "0.112300"; + // expandExponential(1000000000000000000000000000000) === "1000000000000000000000000000000"; + function expandExponential(str) { + str = str.replace(/\s*[*xX]\s*10\s*(\^|\*\*)\s*/, "e"); + + return str.replace(/^([+\-])?(\d+)\.?(\d*)[eE]([+\-]?\d+)$/, function(x, s, n, f, c) { + c = +c; + var l = c < 0; + var i = n.length + c; + x = (l ? n : f).length; + c = ((c = Math.abs(c)) >= x ? c - x + l : 0); + var z = (new Array(c + 1)).join("0"); + var r = n + f; + return (s || "") + (l ? r = z + r : r += z).substr(0, i += l ? z.length : 0) + (i < r.length ? "." + r.substr(i) : ""); + }); + } + + s = s.toString(); + if (typeof base === "undefined" || +base === 10) { + s = expandExponential(s); + } + + var prefixRE; + if (typeof base === "undefined") { + prefixRE = '0[xcb]'; + } + else if (base == 16) { + prefixRE = '0x'; + } + else if (base == 8) { + prefixRE = '0c'; + } + else if (base == 2) { + prefixRE = '0b'; + } + else { + prefixRE = ''; + } + var parts = new RegExp('^([+\\-]?)(' + prefixRE + ')?([0-9a-z]*)(?:\\.\\d*)?$', 'i').exec(s); + if (parts) { + var sign = parts[1] || "+"; + var baseSection = parts[2] || ""; + var digits = parts[3] || ""; + + if (typeof base === "undefined") { + // Guess base + if (baseSection === "0x" || baseSection === "0X") { // Hex + base = 16; + } + else if (baseSection === "0c" || baseSection === "0C") { // Octal + base = 8; + } + else if (baseSection === "0b" || baseSection === "0B") { // Binary + base = 2; + } + else { + base = 10; + } + } + else if (base < 2 || base > 36) { + throw new Error("Illegal radix " + base + "."); + } + + base = +base; + + // Check for digits outside the range + if (!(BigInteger.radixRegex[base].test(digits))) { + throw new Error("Bad digit for radix " + base); + } + + // Strip leading zeros, and convert to array + digits = digits.replace(/^0+/, "").split(""); + if (digits.length === 0) { + return ZERO; + } + + // Get the sign (we know it's not zero) + sign = (sign === "-") ? -1 : 1; + + // Optimize 10 + if (base == 10) { + var d = []; + while (digits.length >= BigInteger_base_log10) { + d.push(parseInt(digits.splice(digits.length-BigInteger.base_log10, BigInteger.base_log10).join(''), 10)); + } + d.push(parseInt(digits.join(''), 10)); + return new BigInteger(d, sign, CONSTRUCT); + } + + // Do the conversion + var d = ZERO; + base = BigInteger.small[base]; + var small = BigInteger.small; + for (var i = 0; i < digits.length; i++) { + d = d.multiply(base).add(small[parseInt(digits[i], 36)]); + } + return new BigInteger(d._d, sign, CONSTRUCT); + } + else { + throw new Error("Invalid BigInteger format: " + s); + } +}; + +/* + Function: add + Add two . + + Parameters: + + n - The number to add to *this*. Will be converted to a . + + Returns: + + The numbers added together. + + See Also: + + , , , +*/ +BigInteger.prototype.add = function(n) { + if (this._s === 0) { + return BigInteger(n); + } + + n = BigInteger(n); + if (n._s === 0) { + return this; + } + if (this._s !== n._s) { + n = n.negate(); + return this.subtract(n); + } + + var a = this._d; + var b = n._d; + var al = a.length; + var bl = b.length; + var sum = new Array(Math.max(al, bl) + 1); + var size = Math.min(al, bl); + var carry = 0; + var digit; + + for (var i = 0; i < size; i++) { + digit = a[i] + b[i] + carry; + sum[i] = digit % BigInteger_base; + carry = (digit / BigInteger_base) | 0; + } + if (bl > al) { + a = b; + al = bl; + } + for (i = size; carry && i < al; i++) { + digit = a[i] + carry; + sum[i] = digit % BigInteger_base; + carry = (digit / BigInteger_base) | 0; + } + if (carry) { + sum[i] = carry; + } + + for ( ; i < al; i++) { + sum[i] = a[i]; + } + + return new BigInteger(sum, this._s, CONSTRUCT); +}; + +/* + Function: negate + Get the additive inverse of a . + + Returns: + + A with the same magnatude, but with the opposite sign. + + See Also: + + +*/ +BigInteger.prototype.negate = function() { + return new BigInteger(this._d, (-this._s) | 0, CONSTRUCT); +}; + +/* + Function: abs + Get the absolute value of a . + + Returns: + + A with the same magnatude, but always positive (or zero). + + See Also: + + +*/ +BigInteger.prototype.abs = function() { + return (this._s < 0) ? this.negate() : this; +}; + +/* + Function: subtract + Subtract two . + + Parameters: + + n - The number to subtract from *this*. Will be converted to a . + + Returns: + + The *n* subtracted from *this*. + + See Also: + + , , , +*/ +BigInteger.prototype.subtract = function(n) { + if (this._s === 0) { + return BigInteger(n).negate(); + } + + n = BigInteger(n); + if (n._s === 0) { + return this; + } + if (this._s !== n._s) { + n = n.negate(); + return this.add(n); + } + + var m = this; + // negative - negative => -|a| - -|b| => -|a| + |b| => |b| - |a| + if (this._s < 0) { + m = new BigInteger(n._d, 1, CONSTRUCT); + n = new BigInteger(this._d, 1, CONSTRUCT); + } + + // Both are positive => a - b + var sign = m.compareAbs(n); + if (sign === 0) { + return ZERO; + } + else if (sign < 0) { + // swap m and n + var t = n; + n = m; + m = t; + } + + // a > b + var a = m._d; + var b = n._d; + var al = a.length; + var bl = b.length; + var diff = new Array(al); // al >= bl since a > b + var borrow = 0; + var i; + var digit; + + for (i = 0; i < bl; i++) { + digit = a[i] - borrow - b[i]; + if (digit < 0) { + digit += BigInteger_base; + borrow = 1; + } + else { + borrow = 0; + } + diff[i] = digit; + } + for (i = bl; i < al; i++) { + digit = a[i] - borrow; + if (digit < 0) { + digit += BigInteger_base; + } + else { + diff[i++] = digit; + break; + } + diff[i] = digit; + } + for ( ; i < al; i++) { + diff[i] = a[i]; + } + + return new BigInteger(diff, sign, CONSTRUCT); +}; + +(function() { + function addOne(n, sign) { + var a = n._d; + var sum = a.slice(); + var carry = true; + var i = 0; + + while (true) { + var digit = (a[i] || 0) + 1; + sum[i] = digit % BigInteger_base; + if (digit <= BigInteger_base - 1) { + break; + } + ++i; + } + + return new BigInteger(sum, sign, CONSTRUCT); + } + + function subtractOne(n, sign) { + var a = n._d; + var sum = a.slice(); + var borrow = true; + var i = 0; + + while (true) { + var digit = (a[i] || 0) - 1; + if (digit < 0) { + sum[i] = digit + BigInteger_base; + } + else { + sum[i] = digit; + break; + } + ++i; + } + + return new BigInteger(sum, sign, CONSTRUCT); + } + + /* + Function: next + Get the next (add one). + + Returns: + + *this* + 1. + + See Also: + + , + */ + BigInteger.prototype.next = function() { + switch (this._s) { + case 0: + return ONE; + case -1: + return subtractOne(this, -1); + // case 1: + default: + return addOne(this, 1); + } + }; + + /* + Function: prev + Get the previous (subtract one). + + Returns: + + *this* - 1. + + See Also: + + , + */ + BigInteger.prototype.prev = function() { + switch (this._s) { + case 0: + return M_ONE; + case -1: + return addOne(this, -1); + // case 1: + default: + return subtractOne(this, 1); + } + }; +})(); + +/* + Function: compareAbs + Compare the absolute value of two . + + Calling is faster than calling twice, then . + + Parameters: + + n - The number to compare to *this*. Will be converted to a . + + Returns: + + -1, 0, or +1 if *|this|* is less than, equal to, or greater than *|n|*. + + See Also: + + , +*/ +BigInteger.prototype.compareAbs = function(n) { + if (this === n) { + return 0; + } + + if (!(n instanceof BigInteger)) { + if (!isFinite(n)) { + return(isNaN(n) ? n : -1); + } + n = BigInteger(n); + } + + if (this._s === 0) { + return (n._s !== 0) ? -1 : 0; + } + if (n._s === 0) { + return 1; + } + + var l = this._d.length; + var nl = n._d.length; + if (l < nl) { + return -1; + } + else if (l > nl) { + return 1; + } + + var a = this._d; + var b = n._d; + for (var i = l-1; i >= 0; i--) { + if (a[i] !== b[i]) { + return a[i] < b[i] ? -1 : 1; + } + } + + return 0; +}; + +/* + Function: compare + Compare two . + + Parameters: + + n - The number to compare to *this*. Will be converted to a . + + Returns: + + -1, 0, or +1 if *this* is less than, equal to, or greater than *n*. + + See Also: + + , , , +*/ +BigInteger.prototype.compare = function(n) { + if (this === n) { + return 0; + } + + n = BigInteger(n); + + if (this._s === 0) { + return -n._s; + } + + if (this._s === n._s) { // both positive or both negative + var cmp = this.compareAbs(n); + return cmp * this._s; + } + else { + return this._s; + } +}; + +/* + Function: isUnit + Return true iff *this* is either 1 or -1. + + Returns: + + true if *this* compares equal to or . + + See Also: + + , , , , , + , +*/ +BigInteger.prototype.isUnit = function() { + return this === ONE || + this === M_ONE || + (this._d.length === 1 && this._d[0] === 1); +}; + +/* + Function: multiply + Multiply two . + + Parameters: + + n - The number to multiply *this* by. Will be converted to a + . + + Returns: + + The numbers multiplied together. + + See Also: + + , , , +*/ +BigInteger.prototype.multiply = function(n) { + // TODO: Consider adding Karatsuba multiplication for large numbers + if (this._s === 0) { + return ZERO; + } + + n = BigInteger(n); + if (n._s === 0) { + return ZERO; + } + if (this.isUnit()) { + if (this._s < 0) { + return n.negate(); + } + return n; + } + if (n.isUnit()) { + if (n._s < 0) { + return this.negate(); + } + return this; + } + if (this === n) { + return this.square(); + } + + var r = (this._d.length >= n._d.length); + var a = (r ? this : n)._d; // a will be longer than b + var b = (r ? n : this)._d; + var al = a.length; + var bl = b.length; + + var pl = al + bl; + var partial = new Array(pl); + var i; + for (i = 0; i < pl; i++) { + partial[i] = 0; + } + + for (i = 0; i < bl; i++) { + var carry = 0; + var bi = b[i]; + var jlimit = al + i; + var digit; + for (var j = i; j < jlimit; j++) { + digit = partial[j] + bi * a[j - i] + carry; + carry = (digit / BigInteger_base) | 0; + partial[j] = (digit % BigInteger_base) | 0; + } + if (carry) { + digit = partial[j] + carry; + carry = (digit / BigInteger_base) | 0; + partial[j] = digit % BigInteger_base; + } + } + return new BigInteger(partial, this._s * n._s, CONSTRUCT); +}; + +// Multiply a BigInteger by a single-digit native number +// Assumes that this and n are >= 0 +// This is not really intended to be used outside the library itself +BigInteger.prototype.multiplySingleDigit = function(n) { + if (n === 0 || this._s === 0) { + return ZERO; + } + if (n === 1) { + return this; + } + + var digit; + if (this._d.length === 1) { + digit = this._d[0] * n; + if (digit >= BigInteger_base) { + return new BigInteger([(digit % BigInteger_base)|0, + (digit / BigInteger_base)|0], 1, CONSTRUCT); + } + return new BigInteger([digit], 1, CONSTRUCT); + } + + if (n === 2) { + return this.add(this); + } + if (this.isUnit()) { + return new BigInteger([n], 1, CONSTRUCT); + } + + var a = this._d; + var al = a.length; + + var pl = al + 1; + var partial = new Array(pl); + for (var i = 0; i < pl; i++) { + partial[i] = 0; + } + + var carry = 0; + for (var j = 0; j < al; j++) { + digit = n * a[j] + carry; + carry = (digit / BigInteger_base) | 0; + partial[j] = (digit % BigInteger_base) | 0; + } + if (carry) { + partial[j] = carry; + } + + return new BigInteger(partial, 1, CONSTRUCT); +}; + +/* + Function: square + Multiply a by itself. + + This is slightly faster than regular multiplication, since it removes the + duplicated multiplcations. + + Returns: + + > this.multiply(this) + + See Also: + +*/ +BigInteger.prototype.square = function() { + // Normally, squaring a 10-digit number would take 100 multiplications. + // Of these 10 are unique diagonals, of the remaining 90 (100-10), 45 are repeated. + // This procedure saves (N*(N-1))/2 multiplications, (e.g., 45 of 100 multiplies). + // Based on code by Gary Darby, Intellitech Systems Inc., www.DelphiForFun.org + + if (this._s === 0) { + return ZERO; + } + if (this.isUnit()) { + return ONE; + } + + var digits = this._d; + var length = digits.length; + var imult1 = new Array(length + length + 1); + var product, carry, k; + var i; + + // Calculate diagonal + for (i = 0; i < length; i++) { + k = i * 2; + product = digits[i] * digits[i]; + carry = (product / BigInteger_base) | 0; + imult1[k] = product % BigInteger_base; + imult1[k + 1] = carry; + } + + // Calculate repeating part + for (i = 0; i < length; i++) { + carry = 0; + k = i * 2 + 1; + for (var j = i + 1; j < length; j++, k++) { + product = digits[j] * digits[i] * 2 + imult1[k] + carry; + carry = (product / BigInteger_base) | 0; + imult1[k] = product % BigInteger_base; + } + k = length + i; + var digit = carry + imult1[k]; + carry = (digit / BigInteger_base) | 0; + imult1[k] = digit % BigInteger_base; + imult1[k + 1] += carry; + } + + return new BigInteger(imult1, 1, CONSTRUCT); +}; + +/* + Function: quotient + Divide two and truncate towards zero. + + throws an exception if *n* is zero. + + Parameters: + + n - The number to divide *this* by. Will be converted to a . + + Returns: + + The *this* / *n*, truncated to an integer. + + See Also: + + , , , , +*/ +BigInteger.prototype.quotient = function(n) { + return this.divRem(n)[0]; +}; + +/* + Function: divide + Deprecated synonym for . +*/ +BigInteger.prototype.divide = BigInteger.prototype.quotient; + +/* + Function: remainder + Calculate the remainder of two . + + throws an exception if *n* is zero. + + Parameters: + + n - The remainder after *this* is divided *this* by *n*. Will be + converted to a . + + Returns: + + *this* % *n*. + + See Also: + + , +*/ +BigInteger.prototype.remainder = function(n) { + return this.divRem(n)[1]; +}; + +/* + Function: divRem + Calculate the integer quotient and remainder of two . + + throws an exception if *n* is zero. + + Parameters: + + n - The number to divide *this* by. Will be converted to a . + + Returns: + + A two-element array containing the quotient and the remainder. + + > a.divRem(b) + + is exactly equivalent to + + > [a.quotient(b), a.remainder(b)] + + except it is faster, because they are calculated at the same time. + + See Also: + + , +*/ +BigInteger.prototype.divRem = function(n) { + n = BigInteger(n); + if (n._s === 0) { + throw new Error("Divide by zero"); + } + if (this._s === 0) { + return [ZERO, ZERO]; + } + if (n._d.length === 1) { + return this.divRemSmall(n._s * n._d[0]); + } + + // Test for easy cases -- |n1| <= |n2| + switch (this.compareAbs(n)) { + case 0: // n1 == n2 + return [this._s === n._s ? ONE : M_ONE, ZERO]; + case -1: // |n1| < |n2| + return [ZERO, this]; + } + + var sign = this._s * n._s; + var a = n.abs(); + var b_digits = this._d; + var b_index = b_digits.length; + var digits = n._d.length; + var quot = []; + var guess; + + var part = new BigInteger([], 0, CONSTRUCT); + part._s = 1; + + while (b_index) { + part._d.unshift(b_digits[--b_index]); + + if (part.compareAbs(n) < 0) { + quot.push(0); + continue; + } + if (part._s === 0) { + guess = 0; + } + else { + var xlen = part._d.length, ylen = a._d.length; + var highx = part._d[xlen-1]*BigInteger_base + part._d[xlen-2]; + var highy = a._d[ylen-1]*BigInteger_base + a._d[ylen-2]; + if (part._d.length > a._d.length) { + // The length of part._d can either match a._d length, + // or exceed it by one. + highx = (highx+1)*BigInteger_base; + } + guess = Math.ceil(highx/highy); + } + do { + var check = a.multiplySingleDigit(guess); + if (check.compareAbs(part) <= 0) { + break; + } + guess--; + } while (guess); + + quot.push(guess); + if (!guess) { + continue; + } + var diff = part.subtract(check); + part._d = diff._d.slice(); + if (part._d.length === 0) { + part._s = 0; + } + } + + return [new BigInteger(quot.reverse(), sign, CONSTRUCT), + new BigInteger(part._d, this._s, CONSTRUCT)]; +}; + +// Throws an exception if n is outside of (-BigInteger.base, -1] or +// [1, BigInteger.base). It's not necessary to call this, since the +// other division functions will call it if they are able to. +BigInteger.prototype.divRemSmall = function(n) { + var r; + n = +n; + if (n === 0) { + throw new Error("Divide by zero"); + } + + var n_s = n < 0 ? -1 : 1; + var sign = this._s * n_s; + n = Math.abs(n); + + if (n < 1 || n >= BigInteger_base) { + throw new Error("Argument out of range"); + } + + if (this._s === 0) { + return [ZERO, ZERO]; + } + + if (n === 1 || n === -1) { + return [(sign === 1) ? this.abs() : new BigInteger(this._d, sign, CONSTRUCT), ZERO]; + } + + // 2 <= n < BigInteger_base + + // divide a single digit by a single digit + if (this._d.length === 1) { + var q = new BigInteger([(this._d[0] / n) | 0], 1, CONSTRUCT); + r = new BigInteger([(this._d[0] % n) | 0], 1, CONSTRUCT); + if (sign < 0) { + q = q.negate(); + } + if (this._s < 0) { + r = r.negate(); + } + return [q, r]; + } + + var digits = this._d.slice(); + var quot = new Array(digits.length); + var part = 0; + var diff = 0; + var i = 0; + var guess; + + while (digits.length) { + part = part * BigInteger_base + digits[digits.length - 1]; + if (part < n) { + quot[i++] = 0; + digits.pop(); + diff = BigInteger_base * diff + part; + continue; + } + if (part === 0) { + guess = 0; + } + else { + guess = (part / n) | 0; + } + + var check = n * guess; + diff = part - check; + quot[i++] = guess; + if (!guess) { + digits.pop(); + continue; + } + + digits.pop(); + part = diff; + } + + r = new BigInteger([diff], 1, CONSTRUCT); + if (this._s < 0) { + r = r.negate(); + } + return [new BigInteger(quot.reverse(), sign, CONSTRUCT), r]; +}; + +/* + Function: isEven + Return true iff *this* is divisible by two. + + Note that is even. + + Returns: + + true if *this* is even, false otherwise. + + See Also: + + +*/ +BigInteger.prototype.isEven = function() { + var digits = this._d; + return this._s === 0 || digits.length === 0 || (digits[0] % 2) === 0; +}; + +/* + Function: isOdd + Return true iff *this* is not divisible by two. + + Returns: + + true if *this* is odd, false otherwise. + + See Also: + + +*/ +BigInteger.prototype.isOdd = function() { + return !this.isEven(); +}; + +/* + Function: sign + Get the sign of a . + + Returns: + + * -1 if *this* < 0 + * 0 if *this* == 0 + * +1 if *this* > 0 + + See Also: + + , , , , +*/ +BigInteger.prototype.sign = function() { + return this._s; +}; + +/* + Function: isPositive + Return true iff *this* > 0. + + Returns: + + true if *this*.compare() == 1. + + See Also: + + , , , , , +*/ +BigInteger.prototype.isPositive = function() { + return this._s > 0; +}; + +/* + Function: isNegative + Return true iff *this* < 0. + + Returns: + + true if *this*.compare() == -1. + + See Also: + + , , , , , +*/ +BigInteger.prototype.isNegative = function() { + return this._s < 0; +}; + +/* + Function: isZero + Return true iff *this* == 0. + + Returns: + + true if *this*.compare() == 0. + + See Also: + + , , , , +*/ +BigInteger.prototype.isZero = function() { + return this._s === 0; +}; + +/* + Function: exp10 + Multiply a by a power of 10. + + This is equivalent to, but faster than + + > if (n >= 0) { + > return this.multiply(BigInteger("1e" + n)); + > } + > else { // n <= 0 + > return this.quotient(BigInteger("1e" + -n)); + > } + + Parameters: + + n - The power of 10 to multiply *this* by. *n* is converted to a + javascipt number and must be no greater than + (0x7FFFFFFF), or an exception will be thrown. + + Returns: + + *this* * (10 ** *n*), truncated to an integer if necessary. + + See Also: + + , +*/ +BigInteger.prototype.exp10 = function(n) { + n = +n; + if (n === 0) { + return this; + } + if (Math.abs(n) > Number(MAX_EXP)) { + throw new Error("exponent too large in BigInteger.exp10"); + } + if (n > 0) { + var k = new BigInteger(this._d.slice(), this._s, CONSTRUCT); + + for (; n >= BigInteger_base_log10; n -= BigInteger_base_log10) { + k._d.unshift(0); + } + if (n == 0) + return k; + k._s = 1; + k = k.multiplySingleDigit(Math.pow(10, n)); + return (this._s < 0 ? k.negate() : k); + } else if (-n >= this._d.length*BigInteger_base_log10) { + return ZERO; + } else { + var k = new BigInteger(this._d.slice(), this._s, CONSTRUCT); + + for (n = -n; n >= BigInteger_base_log10; n -= BigInteger_base_log10) { + k._d.shift(); + } + return (n == 0) ? k : k.divRemSmall(Math.pow(10, n))[0]; + } +}; + +/* + Function: pow + Raise a to a power. + + In this implementation, 0**0 is 1. + + Parameters: + + n - The exponent to raise *this* by. *n* must be no greater than + (0x7FFFFFFF), or an exception will be thrown. + + Returns: + + *this* raised to the *nth* power. + + See Also: + + +*/ +BigInteger.prototype.pow = function(n) { + if (this.isUnit()) { + if (this._s > 0) { + return this; + } + else { + return BigInteger(n).isOdd() ? this : this.negate(); + } + } + + n = BigInteger(n); + if (n._s === 0) { + return ONE; + } + else if (n._s < 0) { + if (this._s === 0) { + throw new Error("Divide by zero"); + } + else { + return ZERO; + } + } + if (this._s === 0) { + return ZERO; + } + if (n.isUnit()) { + return this; + } + + if (n.compareAbs(MAX_EXP) > 0) { + throw new Error("exponent too large in BigInteger.pow"); + } + var x = this; + var aux = ONE; + var two = BigInteger.small[2]; + + while (n.isPositive()) { + if (n.isOdd()) { + aux = aux.multiply(x); + if (n.isUnit()) { + return aux; + } + } + x = x.square(); + n = n.quotient(two); + } + + return aux; +}; + +/* + Function: modPow + Raise a to a power (mod m). + + Because it is reduced by a modulus, is not limited by + like . + + Parameters: + + exponent - The exponent to raise *this* by. Must be positive. + modulus - The modulus. + + Returns: + + *this* ^ *exponent* (mod *modulus*). + + See Also: + + , +*/ +BigInteger.prototype.modPow = function(exponent, modulus) { + var result = ONE; + var base = this; + + while (exponent.isPositive()) { + if (exponent.isOdd()) { + result = result.multiply(base).remainder(modulus); + } + + exponent = exponent.quotient(BigInteger.small[2]); + if (exponent.isPositive()) { + base = base.square().remainder(modulus); + } + } + + return result; +}; + +/* + Function: log + Get the natural logarithm of a as a native JavaScript number. + + This is equivalent to + + > Math.log(this.toJSValue()) + + but handles values outside of the native number range. + + Returns: + + log( *this* ) + + See Also: + + +*/ +BigInteger.prototype.log = function() { + switch (this._s) { + case 0: return -Infinity; + case -1: return NaN; + default: // Fall through. + } + + var l = this._d.length; + + if (l*BigInteger_base_log10 < 30) { + return Math.log(this.valueOf()); + } + + var N = Math.ceil(30/BigInteger_base_log10); + var firstNdigits = this._d.slice(l - N); + return Math.log((new BigInteger(firstNdigits, 1, CONSTRUCT)).valueOf()) + (l - N) * Math.log(BigInteger_base); +}; + +/* + Function: valueOf + Convert a to a native JavaScript integer. + + This is called automatically by JavaScipt to convert a to a + native value. + + Returns: + + > parseInt(this.toString(), 10) + + See Also: + + , +*/ +BigInteger.prototype.valueOf = function() { + return parseInt(this.toString(), 10); +}; + +/* + Function: toJSValue + Convert a to a native JavaScript integer. + + This is the same as valueOf, but more explicitly named. + + Returns: + + > parseInt(this.toString(), 10) + + See Also: + + , +*/ +BigInteger.prototype.toJSValue = function() { + return parseInt(this.toString(), 10); +}; + +var MAX_EXP = BigInteger(0x7FFFFFFF); +// Constant: MAX_EXP +// The largest exponent allowed in and (0x7FFFFFFF or 2147483647). +BigInteger.MAX_EXP = MAX_EXP; + +(function() { + function makeUnary(fn) { + return function(a) { + return fn.call(BigInteger(a)); + }; + } + + function makeBinary(fn) { + return function(a, b) { + return fn.call(BigInteger(a), BigInteger(b)); + }; + } + + function makeTrinary(fn) { + return function(a, b, c) { + return fn.call(BigInteger(a), BigInteger(b), BigInteger(c)); + }; + } + + (function() { + var i, fn; + var unary = "toJSValue,isEven,isOdd,sign,isZero,isNegative,abs,isUnit,square,negate,isPositive,toString,next,prev,log".split(","); + var binary = "compare,remainder,divRem,subtract,add,quotient,divide,multiply,pow,compareAbs".split(","); + var trinary = ["modPow"]; + + for (i = 0; i < unary.length; i++) { + fn = unary[i]; + BigInteger[fn] = makeUnary(BigInteger.prototype[fn]); + } + + for (i = 0; i < binary.length; i++) { + fn = binary[i]; + BigInteger[fn] = makeBinary(BigInteger.prototype[fn]); + } + + for (i = 0; i < trinary.length; i++) { + fn = trinary[i]; + BigInteger[fn] = makeTrinary(BigInteger.prototype[fn]); + } + + BigInteger.exp10 = function(x, n) { + return BigInteger(x).exp10(n); + }; + })(); +})(); + +exports.BigInteger = BigInteger; +})(biginteger); diff --git a/ui.js b/ui.js new file mode 100644 index 0000000..adc4275 --- /dev/null +++ b/ui.js @@ -0,0 +1,380 @@ +// This code was lifted from http://www.squarefree.com/shell/shell.html +// Probably originally by Jesse Ruderman. + +var + histList = [""], + histPos = 0, + _win, // a top-level context + question, + _in, + _out, + tooManyMatches = null, + lastError = null; + +function refocus() { + _in.blur(); // Needed for Mozilla to scroll correctly. + _in.focus(); +} + +var mode = 'calculator'; + +function init() { + _in = document.getElementById("input"); + _out = document.getElementById("output"); + _win = window; + _win.Shell = window; + recalculateInputHeight(); + refocus(); + + var sourceCode = document.getElementById("sourcecode"); + function setMode(newMode) { + mode = newMode; + + while (sourceCode.lastChild) + sourceCode.removeChild(sourceCode.lastChild); + sourceCode.appendChild(document.createTextNode(parseModes[mode].toString())); + } + + setMode('calc'); + + var radios = document.getElementsByTagName("input"); + for (var i = 0; i < radios.length; i++) { + var e = radios[i]; + if (e.type == "radio") { + e.addEventListener("click", function () { + setMode(this.value); + setTimeout(refocus, 0); + return true; + }); + if (e.value == 'calc') + e.checked = true; + } + } +} + +// Unless the user is selected something, refocus the textbox. +// (requested by caillon, brendan, asa) +function keepFocusInTextbox(e) { + var g = e.srcElement ? e.srcElement : e.target; // IE vs. standard + + while (!g.tagName) + g = g.parentNode; + var t = g.tagName.toUpperCase(); + if (t=="A" || t=="INPUT") + return; + + if (window.getSelection) { + // Mozilla + if (String(window.getSelection())) + return; + } else if (document.getSelection) { + // Opera? Netscape 4? + if (document.getSelection()) + return; + } else { + // IE + if ( document.selection.createRange().text ) + return; + } + + refocus(); +} + +function inputKeydown(e) { + // Use onkeydown because IE doesn't support onkeypress for arrow keys + + //alert(e.keyCode + " ^ " + e.keycode); + + if (e.shiftKey && e.keyCode == 13) { // shift-enter + // don't do anything; allow the shift-enter to insert a line break as normal + } else if (e.keyCode == 13) { // enter + // execute the input on enter + try { go(); } catch(er) { alert(er); }; + setTimeout(function() { _in.value = ""; }, 0); // can't preventDefault on input, so clear it later + } else if (e.keyCode == 38) { // up + // go up in history if at top or ctrl-up + if (e.ctrlKey || caretInFirstLine(_in)) + hist(true); + } else if (e.keyCode == 40) { // down + // go down in history if at end or ctrl-down + if (e.ctrlKey || caretInLastLine(_in)) + hist(false); + } else if (e.keyCode == 9) { // tab + setTimeout(function() { refocus(); }, 0); // refocus because tab was hit + } else { } + + setTimeout(recalculateInputHeight, 0); + + //return true; +} + +function caretInFirstLine(textbox) { + // IE doesn't support selectionStart/selectionEnd + if (textbox.selectionStart == undefined) + return true; + + var firstLineBreak = textbox.value.indexOf("\n"); + + return ((firstLineBreak == -1) || (textbox.selectionStart <= firstLineBreak)); +} + +function caretInLastLine(textbox) { + // IE doesn't support selectionStart/selectionEnd + if (textbox.selectionEnd == undefined) + return true; + + var lastLineBreak = textbox.value.lastIndexOf("\n"); + + return (textbox.selectionEnd > lastLineBreak); +} + +function recalculateInputHeight() { + var rows = _in.value.split(/\n/).length + + 1 // prevent scrollbar flickering in Mozilla + + (window.opera ? 1 : 0); // leave room for scrollbar in Opera + + if (_in.rows != rows) // without this check, it is impossible to select text in Opera 7.60 or Opera 8.0. + _in.rows = rows; +} + +function writeNode(type, node) { + var newdiv = document.createElement("div"); + newdiv.className = type; + newdiv.appendChild(node); + _out.appendChild(newdiv); + return newdiv; +} + +function println(s, type) { + s = String(s); + if (s) + return writeNode(type, document.createTextNode(s)); +} + +function printWithRunin(h, s, type) { + var div = println(s, type); + var head = document.createElement("strong"); + head.appendChild(document.createTextNode(h + ": ")); + div.insertBefore(head, div.firstChild); +} + +function hist(up) { + // histList[0] = first command entered, [1] = second, etc. + // type something, press up --> thing typed is now in "limbo" + // (last item in histList) and should be reachable by pressing + // down again. + + var L = histList.length; + + if (L == 1) + return; + + if (up) { + if (histPos == L-1) { + // Save this entry in case the user hits the down key. + histList[histPos] = _in.value; + } + + if (histPos > 0) { + histPos--; + // Use a timeout to prevent up from moving cursor within new text + // Set to nothing first for the same reason + setTimeout( + function() { + _in.value = ''; + _in.value = histList[histPos]; + var caretPos = _in.value.length; + if (_in.setSelectionRange) + _in.setSelectionRange(caretPos, caretPos); + }, + 0 + ); + } + } else { // down + if (histPos < L-1) { + histPos++; + _in.value = histList[histPos]; + } else if (histPos == L-1) { + // Already on the current entry: clear but save + if (_in.value) { + histList[histPos] = _in.value; + ++histPos; + _in.value = ""; + } + } + } +} + +function printQuestion(q) { + println(q, "input"); +} + +function printAnswer(a) { + if (a !== undefined) + println(a, "normalOutput"); +} + +function printError(er) { + var lineNumberString; + + lastError = er; // for debugging the shell + if (er.name) { + // lineNumberString should not be "", to avoid a very wacky bug in IE 6. + lineNumberString = (er.lineNumber != undefined) ? (" on line " + er.lineNumber + ": ") : ": "; + println(er.name + lineNumberString + er.message, "error"); // Because IE doesn't have error.toString. + } else { + println(er, "error"); // Because security errors in Moz /only/ have toString. + } +} + +// Example: Taylor series for sin: +// x * (1 - x*x/(2*3) * (1 - x*x/(4*5) * (1 - x*x/(6*7) * (1 - x*x/(8*9) * (1 - x*x/(10*11) * (1 - x*x/(12*13))))))) +function showPlot(fn) { + var c = document.createElement('canvas'); + c.width = 600; + c.height = 400; + writeNode("normalOutput", c); + var ctx = c.getContext("2d"); + + var rawData = [], labels = []; + for (var i = 0; i <= 100; i++) { + var x = (i - 50) / 20; + rawData[i] = fn(x); + if (i % 10 == 0) + labels[i] = x; + else + labels[i] = ""; + } + + var chart = new Chart(ctx).Line({ + labels: labels, + datasets: [ + { + fillColor : "rgba(151,187,205,0.5)", + strokeColor : "rgba(151,187,205,1)", + data : rawData + } + ] + }, { + bezierCurve: false, + pointDot: false, + animation: false + }); +} + +function showComplexPlot(fn) { + var WIDTH = 401, HEIGHT = 401; + var c = document.createElement('canvas'); + c.width = WIDTH; + c.height = HEIGHT; + writeNode("normalOutput", c); + var ctx = c.getContext('2d'); + var imageData = ctx.getImageData(0, 0, WIDTH, HEIGHT); + var arr = imageData.data; + + var i = 0; + for (var yi = 0; yi < HEIGHT; yi++) { + var z_im = ((HEIGHT - 1) / 2 - yi) * (6 / HEIGHT); + for (var xi = 0; xi < WIDTH; xi++) { + var z_re = (xi - (WIDTH - 1) / 2) * (6 / WIDTH); + var result = fn(z_re, z_im); + + var rabs = Math.sqrt(result.re*result.re + result.im*result.im); + + var h = Math.atan2(result.im, result.re) / (2*Math.PI); + var s = 1.0; + var l; + if (result.re !== result.re || result.im !== result.im) { + l = 1; + } else { + l = 0.5 + Math.log(rabs + 0.5) / 3; + if (l > 1) + l = 1; + else if (l < 0) + l = 0; + } + + var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; + var m1 = l * 2 - m2; + + function hue(h) { + h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h); + if (h * 6 < 1) return m1 + (m2 - m1) * h * 6; + else if (h * 2 < 1) return m2; + else if (h * 3 < 2) return m1 + (m2 - m1) * (2/3 - h) * 6; + else return m1; + } + + arr[i] = Math.round(255 * hue(h + 1/3)); + arr[i + 1] = Math.round(255 * hue(h)); + arr[i + 2] = Math.round(255 * hue(h - 1/3)); + arr[i + 3] = 255; + i += 4; + } + } + + ctx.putImageData(imageData, 0, 0); +} + + +var mode = 'calc'; + +function go() { + question = _in.value; + + if (question == "") + return; + + histList[histList.length-1] = question; + histList[histList.length] = ""; + histPos = histList.length - 1; + + _in.value=''; + recalculateInputHeight(); + printQuestion(question); + + if (_win.closed) { + printError("Target window has been closed."); + return; + } + + if (!("Shell" in _win)) + initTarget(); // silent + + try { + var result = parseModes[mode](question); + switch (mode) { + case 'calc': + Shell.printAnswer(result); + break; + + case 'fraction': + Shell.printAnswer(result); + break; + + case 'blocks': + writeNode("normalOutput", result); + break; + + case 'json': + Shell.printAnswer(JSON.stringify(result, undefined, " ")); + break; + + case 'mathml': + writeNode("normalOutput", result.element); + break; + + case 'graph': + showPlot(result); + break; + + case 'complex': + showComplexPlot(result); + break; + } + } catch (exc) { + Shell.printError(exc); + } + setTimeout(Shell.refocus, 0); +} +