define(['i18n!calculator.command', 'jquery'] , function (I18n, $){ var calcCmd = { } ; (function (){ var methods = { } ; var predefinedVariables = { } ; var variables = { } ; var lastComputedResult; var expressions = [{ regex: /\s+/, token: 'whitespace'} , { regex: /[a-zA-Z][a-zA-Z0-9_\.]*/, token: 'variable'} , { regex: /[0-9]*\.?[0-9]+/, token: 'number'} , { regex: /\+/, token: 'add'} , { regex: /\-/, token: 'subtract'} , { regex: /\*/, token: 'multiply'} , { regex: /\//, token: 'divide'} , { regex: /\(/, token: 'open_paren'} , { regex: /\)/, token: 'close_paren'} , { regex: /\,/, token: 'comma'} , { regex: /\^/, token: 'power'} , { regex: /\=/, token: 'equals'} ] ; var parseToken = function (command, index){ var value = command.substring(index); var item = { } ; for (var idx in expressions){ var expression = expressions[idx]; var match = value.match(expression.regex); if (match && match[0] && value.indexOf(match[0]) == 0) { item.token = expression.token; item.value = match[0]; item.newIndex = index + _AN_Read_length('length', match[0]); return item; } } return null ; } ; var parseSyntax = function (command){ var index = 0; var result = [] ; while (index < _AN_Read_length('length', command)){ var item = parseToken(command, index); if (!item) { throw ("unrecognized token at " + index) } index = item.newIndex; result.push(item); } return result; } ; var syntaxIndex = 0; var parseArgument = function (syntax){ var result = null ; switch (syntax[syntaxIndex].token){ case 'number': result = syntax[syntaxIndex]; break ; case 'subtract': if (syntax[syntaxIndex + 1] && (syntax[syntaxIndex + 1].token == 'number' || syntax[syntaxIndex + 1].token == 'variable')) { syntax[syntaxIndex + 1].value = "-" + syntax[syntaxIndex + 1].value; syntaxIndex++ ; result = syntax[syntaxIndex]; } else { throw ("expecting a number at " + syntax[syntaxIndex].newIndex) } break ; case 'variable': if (syntax[syntaxIndex + 1] && syntax[syntaxIndex + 1].token == 'open_paren') { result = syntax[syntaxIndex]; result.token = 'method'; result.arguments = [] ; var ender = 'comma'; syntaxIndex = syntaxIndex + 2; if (syntax[syntaxIndex].token == 'close_paren') { ender = 'close_paren'; syntaxIndex++ ; } while (ender == 'comma'){ result.arguments.push(parseExpression(syntax, ['comma', 'close_paren'] )); ender = syntax[syntaxIndex].token; syntaxIndex++ ; } syntaxIndex-- ; if (ender != 'close_paren') { throw ("expecting close parenthesis at " + syntax[syntaxIndex].newIndex) } } else { result = syntax[syntaxIndex]; } break ; case 'open_paren': result = syntax[syntaxIndex]; result.token = 'parenthesized_expression'; syntaxIndex++ ; result.expression = parseExpression(syntax, ['close_paren'] ); break ; } if (!result) { var index = (syntax && syntax[syntaxIndex] && syntax[syntaxIndex].newIndex) || 0; var type = (syntax && syntax[syntaxIndex] && syntax[syntaxIndex].token) || "nothing"; throw ("expecting a value at " + index + ", got a " + type) } syntaxIndex++ ; return result; } ; var parseModifier = function (syntax){ switch (syntax[syntaxIndex].token){ case 'add': return syntax[syntaxIndex++ ]; break ; case 'subtract': return syntax[syntaxIndex++ ]; break ; case 'multiply': return syntax[syntaxIndex++ ]; break ; case 'divide': return syntax[syntaxIndex++ ]; break ; case 'power': return syntax[syntaxIndex++ ]; break ; } var value = (syntax && syntax[syntaxIndex] && syntax[syntaxIndex].token) || 'value'; var index = (syntax && syntax[syntaxIndex] && syntax[syntaxIndex].newIndex) || 0; throw ("unexpected " + value + " at " + index) } ; var parseExpression = function (syntax, enders){ var result = { token: 'expression', newIndex: syntax[syntaxIndex].newIndex} ; result.expressionItems = [] ; result.expressionItems.push(parseArgument(syntax)); if (syntaxIndex > _AN_Read_length('length', syntax)) { return result; } var ended = false ; while (syntaxIndex < _AN_Read_length('length', syntax) && !ended){ for (var idx in enders){ if (syntax[syntaxIndex].token == enders[idx]) { ended = true ; } } if (!ended) { result.expressionItems.push(parseModifier(syntax)); result.expressionItems.push(parseArgument(syntax)); } } return result; } ; var parseFullExpression = function (syntax){ var newSyntax = [] ; for (var idx in syntax){ if (syntax[idx].token != 'whitespace') { newSyntax.push(syntax[idx]); } } syntax = newSyntax; var result = null ; syntaxIndex = 0; if (syntax[syntaxIndex].token == 'variable' && _AN_Read_length('length', syntax) > 1 && syntax[syntaxIndex + 1].token == 'equals') { result = { token: 'variable_assignment', newIndex: syntax[syntaxIndex].newIndex} ; result.variable = syntax[syntaxIndex]; if (_AN_Read_length('length', syntax) > 2) { syntaxIndex = 2; result.assignmentExpression = parseExpression(syntax); } else { throw ("Expecting value at " + syntax[syntaxIndex + 1].newIndex) } } else { result = parseExpression(syntax); } return result; } ; var computeExpression = function (tree){ var round0 = tree.expressionItems; var round1 = [round0[0]] ; for (var idx = 1; idx < _AN_Read_length("length", round0); idx += 2){ var item = round0[idx]; if (item.token == 'power') { var left = round1.pop(); var right = round0[idx + 1]; round1.push(numberItem(Math.pow(compute(left), compute(right)))); } else { round1.push(round0[idx]); round1.push(round0[idx + 1]); } } var round2 = [round1[0]] ; for (var idx = 1; idx < _AN_Read_length('length', round1); idx += 2){ var item = round1[idx]; if (item.token == 'multiply') { var left = round2.pop(); var right = round1[idx + 1]; round2.push(numberItem(compute(left) * compute(right))); } else if (item.token == 'divide') { var left = round2.pop(); var right = round1[idx + 1]; round2.push(numberItem(compute(left) / compute(right))); } else { round2.push(round1[idx]); round2.push(round1[idx + 1]); } } var round3 = [round2[0]] ; for (var idx = 1; idx < _AN_Read_length('length', round2); idx += 2){ var item = round2[idx]; if (item.token == 'add') { var left = round3.pop(); var right = round2[idx + 1]; round3.push(numberItem(compute(left) + compute(right))); } else if (item.token == 'subtract') { var left = round3.pop(); var right = round2[idx + 1]; round3.push(numberItem(compute(left) - compute(right))); } else { round3.push(round2[idx]); round3.push(round2[idx + 1]); } } if (_AN_Read_length('length', round3) === 0) { throw ("expressions should have at least one value") } else if (_AN_Read_length("length", round3) > 1) { throw ("unexpected modifier: " + round3[1].token) } else { return compute(round3[0]); } } ; var numberItem = function (number){ return { token: 'number', value: number, calculatedValue: number} ; } ; var compute = function (tree){ switch (tree.token){ case 'number': return parseFloat(tree.value); break ; case 'expression': return computeExpression(tree); break ; case 'parenthesized_expression': return compute(tree.expression); break ; case 'variable_assignment': if (tree.variable.value == '_') { throw ("the variable '_' is reserved") } variables[tree.variable.value] = compute(tree.assignmentExpression); return variables[tree.variable.value]; break ; case 'variable': if (tree.value == '_') { return lastComputedResult || 0; } if (tree.value.indexOf("-") == 0) { var absolute = _AN_Call_replace("replace", tree.value, /^\-/, ""); var value = predefinedVariables && predefinedVariables[absolute]; value = value || (variables && variables[absolute]); value = - value; } else { var value = predefinedVariables && predefinedVariables[tree.value]; value = value || (variables && variables[tree.value]); } if (value == undefined) { throw ("undefined variable " + tree.value) } return value; break ; case 'method': var args = [] ; for (var idx in tree.arguments){ var value = compute(tree.arguments[idx]); tree.arguments[idx].computedValue = value; args.push(value); } if (methods[tree.value]) { return methods[tree.value].apply(null , args); } else { throw ("unrecognized method " + tree.value) } break ; } throw ("Unexpected token type: " + tree.token) } ; calcCmd.clearMemory = function (){ variables = { } ; lastComputedResult = null ; } ; var cached_trees = { } ; calcCmd.compute = function (command){ var result = { } ; command = command.toString(); result.command = command; var tree = cached_trees[command]; if (tree) { result.syntax = tree.syntax; result.tree = tree.tree; } else { result.syntax = parseSyntax(command); result.tree = parseFullExpression(result.syntax); cached_trees[command] = result; } result.computedValue = compute(result.tree); lastComputedResult = result.computedValue; return result; } ; calcCmd.computeValue = function (command){ return calcCmd.compute(command).computedValue; } ; var isFunction = function (arg){ return true ; } ; calcCmd.addFunction = function (methodName, method, description, examples){ if (typeof (methodName) == 'string' && isFunction(method)) { method.friendlyName = methodName; method.description = description; if (typeof (examples) == 'string') { examples = [examples] ; } method.examples = examples; methods[methodName] = method; return true ; } return false ; } ; calcCmd.addPredefinedVariable = function (variableName, value, description){ value = parseFloat(value); if (typeof (variableName) == 'string' && (value || value == 0)) { predefinedVariables[variableName] = value; } } ; calcCmd.functionDescription = function (method){ if (methods[method]) { return methods[method].description || I18n.t('no_description', "No description found for the function, %{functionName}", { functionName: method} ); } else { return I18n.t('unrecognized', "%{functionName} is not a recognized function", { functionName: method} ); } } ; calcCmd.functionExamples = function (method){ if (methods[method]) { return methods[method].examples || [] ; } else { return [] ; } } ; calcCmd.functionList = function (){ var result = [] ; for (var idx in methods){ var method = methods[idx]; result.push([idx, method.description || I18n.t('default_description', "No description given")] ); } result.sort(function (a, b){ if (a[0] > b[0]) { return 1; } else if (a[0] < b[0]) { return -1; } else { return 0; } } ); return result; } ; } )(); (function (){ var p = function (name, value, description){ calcCmd.addPredefinedVariable(name, value, description); } ; var f = function (name, func, description, example){ calcCmd.addFunction(name, func, description, example); } ; p('pi', Math.PI); p('e', Math.exp(1)); f('abs', function (val){ return Math.abs(val); } , I18n.t('abs.description', "Returns the absolute value of the given value"), "abs(x)"); f('asin', function (x){ return Math.asin(x); } , I18n.t('asin.description', "Returns the arcsin of the given value"), "asin(x)"); f('acos', function (x){ return Math.acos(x); } , I18n.t('acos.description', "Returns the arccos of the given value"), "acos(x)"); f('atan', function (x){ return Math.atan(x); } , I18n.t('atan.description', "Returns the arctan of the given value"), "atan(x)"); f('log', function (x, base){ return (Math.log(x) / Math.log(base || 10)); } , I18n.t('log.description', "Returns the log of the given value with an optional base"), "log(x, [base])"); f('ln', function (x){ return Math.log(x); } , I18n.t('ln.description', "Returns the natural log of the given value"), "ln(x)"); f('rad_to_deg', function (x){ return x * 180 / Math.PI; } , I18n.t('rad_to_deg.description', "Returns the given value converted from radians to degrees"), "rad_to_deg(radians)"); f('deg_to_rad', function (x){ return x * Math.PI / 180; } , I18n.t('deg_to_rad.description', "Returns the given value converted from degrees to radians"), "deg_to_rad(degrees)"); f('sin', function (x){ return Math.sin(x); } , I18n.t('sin.description', "Returns the sine of the given value"), "sin(radians)"); f('cos', function (x){ return Math.cos(x); } , I18n.t('cos.description', "Returns the cosine of the given value"), "cos(radians)"); f('tan', function (x){ return Math.tan(x); } , I18n.t('tan.description', "Returns the tangent of the given value"), "tan(radians)"); f('sec', function (x){ return 1 / Math.cos(x); } , I18n.t('sec.description', "Returns the secant of the given value"), "sec(radians)"); f('cosec', function (x){ return 1 / Math.sin(x); } , I18n.t('cosec.description', "Returns the cosecant of the given value"), "cosec(radians)"); f('cotan', function (x){ return 1 / Math.tan(x); } , I18n.t('cotan.description', "Returns the cotangent of the given value"), "cotan(radians)"); f('pi', function (x){ return Math.PI; } , I18n.t('pi.description', "Returns the computed value of pi"), "pi()"); f('if', function (bool, pass, fail){ return bool? pass: fail; } , I18n.t('if.description', "Evaluates the first argument, returns the second argument if it evaluates to a non-zero value, otherwise returns the third value"), "if(bool,success,fail)"); var make_list = function (args){ if (_AN_Read_length("length", args) == 1 && (args[0] instanceof Array)) { return args[0]; } else { return args; } } ; f('max', function (){ var args = make_list(arguments); var max = args[0]; for (var idx = 0; idx < _AN_Read_length('length', args); idx++ ){ max = Math.max(max, args[idx]); } return max; } , I18n.t('max.description', "Returns the highest value in the list"), ["max(a,b,c...)", "max(list)"] ); f('min', function (){ var args = make_list(arguments); var min = args[0]; for (var idx = 0; idx < _AN_Read_length('length', args); idx++ ){ min = Math.min(min, args[idx]); } return min; } , I18n.t('min.description', "Returns the lowest value in the list"), ["min(a,b,c...)", "min(list)"] ); f('sqrt', function (x){ return Math.sqrt(x); } , I18n.t('sqrt.description', "Returns the square root of the given value"), "sqrt(x)"); f('sort', function (x){ var args = make_list(arguments); var list = [] ; for (var idx = 0; idx < _AN_Read_length('length', args); idx++ ){ list.push(args[idx]); } return list.sort(function (a, b){ return a - b; } ); } , I18n.t('sort.description', "Returns the list of values, sorted from lowest to highest"), ["sort(a,b,c...)", "sort(list)"] ); f('reverse', function (x){ var args = make_list(arguments); var list = [] ; for (var idx = 0; idx < _AN_Read_length('length', args); idx++ ){ list.unshift(args[idx]); } return list; } , I18n.t('reverse.description', "Reverses the order of the list of values"), ["reverse(a,b,c...)", "reverse(list)"] ); f('first', function (){ return make_list(arguments)[0]; } , I18n.t('first.description', "Returns the first value in the list"), ["first(a,b,c...)", "first(list)"] ); f('last', function (){ var args = make_list(arguments); return args[_AN_Read_length('length', args) - 1]; } , I18n.t('last.description', "Returns the last value in the list"), ["last(a,b,c...)", "last(list)"] ); f('at', function (list, x){ return list[x]; } , I18n.t('at.description', "Returns the indexed value in the given list"), "at(list,index)"); f('rand', function (x){ return (Math.random() * (x || 1)); } , I18n.t('rand.description', "Returns a random number between zero and the range specified, or one if no number is given"), "rand(x)"); f('length', function (){ return _AN_Read_length('length', make_list(arguments)); } , I18n.t('length.description', "Returns the number of arguments in the given list"), ["length(a,b,c...)", "length(list)"] ); var sum = function (list){ var total = 0; for (var idx = 0; idx < _AN_Read_length("length", list); idx++ ){ if (list[idx]) { total += list[idx]; } } return total; } ; f('mean', function (){ var args = make_list(arguments); return sum(args) / _AN_Read_length('length', args); } , I18n.t('mean.description', "Returns the average mean of the values in the list"), ["mean(a,b,c...)", "mean(list)"] ); f('median', function (){ var args = make_list(arguments); var list = [] ; for (var idx = 0; idx < _AN_Read_length('length', args); idx++ ){ list.push(args[idx]); } var list = list.sort(function (a, b){ return parseFloat(a) - parseFloat(b); } ); if (_AN_Read_length('length', list) % 2 == 1) { return list[Math.floor(_AN_Read_length('length', list) / 2)]; } else { return ((list[Math.round(_AN_Read_length('length', list) / 2)] + list[Math.round(_AN_Read_length('length', list) / 2) - 1]) / 2); } } , I18n.t('median.description', "Returns the median for the list of values"), ["median(a,b,c...)", "median(list)"] ); f('range', function (){ var args = make_list(arguments); var list = [] ; for (var idx = 0; idx < _AN_Read_length('length', args); idx++ ){ list.push(args[idx]); } var list = list.sort(); return list[_AN_Read_length('length', list) - 1] - list[0]; } , I18n.t('range.description', "Returns the range for the list of values"), ["range(a,b,c...)", "range(list)"] ); f('count', function (){ return _AN_Read_length('length', make_list(arguments)); } , I18n.t('count.description', "Returns the number of items in the list"), ["count(a,b,c...)", "count(list)"] ); f('sum', function (){ return sum(make_list(arguments)); } , I18n.t('sum.description', "Returns the sum of the list of values"), ["sum(a,b,c...)", "sum(list)"] ); var factorials = { } ; var fact = function (n){ n = Math.max(parseInt(n), 0); if (n == 0 || n == 1) { return 1; } else if (n > 170) { return Infinity; } else if (factorials[n]) { return factorials[n]; } else { return n * fact(n - 1); } } ; f('fact', function (n){ return fact(n); } , I18n.t('fact.description', "Returns the factorial of the given number"), "fact(n)"); f('perm', function (n, k){ return fact(n) / fact(n - k); } , I18n.t('perm.description', "Returns the permutation result for the given values"), "perm(n, k)"); f('comb', function (n, k){ return fact(n) / (fact(k) * fact(n - k)); } , I18n.t('comb.description', "Returns the combination result for the given values"), "comb(n, k)"); f('ceil', function (x){ return Math.ceil(x); } , I18n.t('ceil.description', "Returns the ceiling for the given value"), "ceil(x)"); f('floor', function (x){ return Math.floor(x); } , I18n.t('floor.description', "Returns the floor for the given value"), "floor(x)"); f('round', function (x){ return Math.round(x); } , I18n.t('round.description', "Returns the given value rounded to the nearest whole number"), "round(x)"); f('e', function (x){ return Math.exp(x || 1); } , I18n.t('e.description', "Returns the value for e"), "e()"); } )(); return calcCmd; } );