0
\$\begingroup\$

I have made a code that can run a calculation from text. When I tested it, I didn't find any bugs. You are sharper than me. Maybe you can discover some bugs in this code. When you know some improvements to this code. It would be nice when you let me know.

I also made a calculator with this code. Of course you can easily do it too. You can make a calculator that works with an input element and it can be runned using the calculate function.

// JavaScript Document "/javascript/calculator.js"
// Defining variables
Math.a = Math.b = Math.c = Math.t = Math.x = Math.y = Math.z = 0;
// Defining root for radix sign
Math.radix = function (y,x) {
	if (typeof x === "number") {
		return Math.pow(x,1/y);
	}
	return Math.sqrt(y);
}
// Defining powers and roots
Math.sqr = function (number) {
	return Math.pow(number,2);
}
Math.cbr = function (number) {
	return Math.pow(number,3);
}
Math.cbrt = function (number) {
	return Math.pow(number,1/3);
}
// Defining angle functions
Math.cot = function (number) {
	return Math.tan(Math.asin(1) - number);
}
Math.acot = function (number) {
	return Math.asin(1) - Math.atan(number);
}
Math.deg2rad = function (number) {
	// It has to be Math.asin(1) because Math.PI returns one decimal too big
	return number * Math.asin(1) / 90;
}
Math.rad2deg = function (number) {
	return number * 90 / Math.asin(1);
}
Math.dsin = function (number) {
	return Math.sin(Math.deg2rad(number));
}
Math.dcos = function (number) {
	return Math.cos(Math.deg2rad(number));
}
Math.dtan = function (number) {
	return Math.tan(Math.deg2rad(number));
}
Math.dcot = function (number) {
	return Math.cot(Math.deg2rad(number));
}
Math.dasin = function (number) {
	return Math.rad2deg(Math.asin(number));
}
Math.dacos = function (number) {
	return Math.rad2deg(Math.acos(number));
}
Math.datan = function (number) {
	return Math.rad2deg(Math.atan(number));
}
Math.dacot = function (number) {
	return Math.rad2deg(Math.acot(number));
}
Math.grad2rad = function (number) {
	return number * Math.asin(1) / 100;
}
Math.rad2grad = function (number) {
	return number * 100 / Math.asin(1);
}
Math.gsin = function (number) {
	return Math.sin(Math.grad2rad(number));
}
Math.gcos = function (number) {
	return Math.cos(Math.grad2rad(number));
}
Math.gtan = function (number) {
	return Math.tan(Math.grad2rad(number));
}
Math.gcot = function (number) {
	return Math.cot(Math.grad2rad(number));
}
Math.gasin = function (number) {
	return Math.rad2grad(Math.asin(number));
}
Math.gacos = function (number) {
	return Math.rad2grad(Math.acos(number));
}
Math.gatan = function (number) {
	return Math.rad2grad(Math.atan(number));
}
Math.gacot = function (number) {
	return Math.rad2grad(Math.acot(number));
}
// Defining function to get the left parameter from an operator
function getLeftParam(text,cursorpos,pattern) {
	var leftText = text.slice(0,cursorpos);
	leftText = leftText.replace(/\s*$/,"");
	if (/[\d]$/.test(leftText)) {
		return String(leftText.match(pattern || /(?:\d*\.)?\d+(?:e[+-]\d+)?$/));
	}
	if (/[^\w$][abctxyz]$/.test(leftText)) {
		return String(leftText.match(/[abctxyz]$/));
	}
	leftText = leftText.split("").reverse().join("");
	if (!(/^\)/.test(leftText))) {
		return;
	}
	var cc = 1, cs = 1;
	while (cc > 0) {
		if (leftText.slice(cs).match(/\(|\)/) == "(") {
			cc--;
		} else if (leftText.slice(cs).match(/\(|\)/) == ")") {
			cc++;
		} else {
			return;
		}
		cs += leftText.slice(cs).search(/\(|\)/) + 1;
	}
	cs += leftText.slice(cs).match(/^(\s?[A-Za-z\d_$]*[A-Za-z_$])?/)[0].length;
	return String(text.slice(leftText.length - cs,cursorpos));
}
// Defining the same for right
function getRightParam(text,cursorpos,pattern) {
	var rightText = text.slice(cursorpos);
	rightText = rightText.replace(/^\s*/,"");
	if (/^-?\d+/.test(rightText)) {
		return String(rightText.match(pattern || /^[+-]?(?:\d*\.)?\d+(?:e[+-]\d+)?/));
	}
	if (/^[abctxyz][^\w$\(]/.test(rightText)) {
		return String(rightText.match(/^\w/));
	}
	if (!(/^(?:[A-Za-z_$][\w$]*\s?)?\(/.test(rightText))) {
		return;
	}
	var cc = 1, cs = rightText.search(/\(|\)/) + 1;
	while (cc > 0) {
		if (rightText.slice(cs).match(/\(|\)/) == ")") {
			cc--;
		} else if (rightText.slice(cs).match(/\(|\)/) == "(") {
			cc++;
		} else {
			return;
		}
		cs += rightText.slice(cs).search(/\(|\)/) + 1;
	}
	return String(text.slice(cursorpos,cursorpos + cs));
}
// Calculation function that can be runned by a script
function calculate(text, anglemode) {
	text = String(text);
	if (text == "") {
		return TypeError("Empty calculation");
	}
	text = text.replace(/\n/g," ");
	text = text.replace(/;/g,",");
	{
		// Checking every variable to make sure you can't run an illegal javascript function
		let vars = text.match(/[\w$]+/g);
		if (vars) {for (var i=0;i<vars.length;i++) {
			if (typeof Math[vars[i]] == "undefined" && isNaN(vars[i])) {
        return ReferenceError("Cannot read property \"" + vars[i] + "\" of \"Math\"");
			}
		}}
	}
	// Angle mode
	if (typeof anglemode == "number") {
		text = text.replace(/([^\w$]|^)(a?(?:cos|sin|tan|cot)(?:[^\w$]|$))/, "$1" + ["d","","g"][anglemode] + "$2");
	}
	// Adding absolute values
	while (typeof text == "string") {
		let cur = text.indexOf("|");
    // Loop untill there is no vertical bar (I couldn't use while (true) because my editor should think it is a mistake)
		if (cur === -1) {
			break;
		}
		if (typeof getRightParam(text,cur+1) == "string") {
			text = text.replace(/\|/,"abs(\n");
		} else if (typeof getLeftParam(text,cur) == "string") {
			text = text.replace(/\|/,"\n)");
		} else {
			return SyntaxError("Unexpected token |");
		}
	}
	// When a double vertical bar is detected, match it as an binary parameter
	text = text.replace(/\n\)abs\(\n/g,"|");
	text = text.replace(/\n/g,"");
	// Adding some parameters
	while (typeof text == "string") {
		let cur = text.indexOf("^");
		if (cur === -1) {
			break;
		}
		let number1 = getLeftParam(text,cur);
		let number2 = getRightParam(text,cur+1);
		if (typeof number1 != "string" || typeof number2 != "string") {
			return SyntaxError("Unexpected token ^");
		}
		text = text.slice(0,text.lastIndexOf(number1,cur)) + "pow(" + number1 + "," + number2 + ")" + text.slice(text.indexOf(number2,cur) + number2.length);
	}
	while (typeof text == "string") {
		let cur = text.indexOf("\u221a");
		if (cur === -1) {
			break;
		}
		let number = getRightParam(text,cur+1,/^[+-]?(?:\d*\.)?\d+(?:e[+-]\d+)?(?:&[+-]?(?:\d*\.)?\d+(?:e[+-]\d+)?)?/);
		if (typeof number != "string") {
			return SyntaxError("Unexpected token \u221a");
		}
		number = number.replace(/&/,",");
		let ilength = number.length;
		if (number[0] != "(") {
			number = "(" + number + ")";
		}
		text = text.slice(0,cur) + "radix" + number + text.slice(ilength + cur + 1);
	}
	// Running the code
	with (Math) {
		try {
			var output = Number(eval(text));
			if (isNaN(output) || typeof output != "number") {
				throw TypeError("The result of \"" + text + "\" is not a valid number");
			}
			if (Math.abs(output) === Infinity) {
				throw ReferenceError("The result of \"" + text + "\" is outside range");
			}
			return output;
		} catch (err) {
			return err;
		}
	}
}
// Run an example
console.log(calculate("c=\u221a(3^2+4^2)"))

\$\endgroup\$
  • \$\begingroup\$ Fixed bug at line 162: Angle mode don't match functions at the start of a text \$\endgroup\$ – Migats21 Feb 3 at 8:34
  • \$\begingroup\$ Fixed bug at line 154: When there is not a variable nor number, the function will break itself instead of returning an error. \$\endgroup\$ – Migats21 Feb 4 at 9:39
  • 1
    \$\begingroup\$ Please stop editing the code in your question, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. \$\endgroup\$ – Mast Feb 4 at 12:13
4
\$\begingroup\$

First off, my basic maths is a bit rusty, but is radix correct? Shouldn't Math.pow(x,1/y) be Math.pow(y,1/x)?

I'm not a big fan of "extending" the native Math object. The Math object isn't a regular JavaScript object so you shouldn't assume that all implementations will allow you to add new properties to it. It would be better to create your own object and copy the properties and methods needed over from Math.

The code in general leans a bit to the cryptic side. Especially the many regular expressions urgently need some comments and the code could be split up into functions with descriptive names.

The use of the String constructor seems unnecessary in most places and could be replaced with more readable alternatives.

Checking the return values from getLeft/RightParam with typeof seems overkill. I'd use a simple truthy check (if (number) {) instead.

The getLeftParam and getRightParam functions share a lot of similar code.

The repeated use of while (typeof text == "string") { looks strange to me, because in as far as I can see in all cases the condition never becomes false.

Error handling seems inconsistent. Sometimes errors are returned, sometimes thrown. And the choices of error objects (ReferenceError, TypeError, etc.) seem arbitrary.

And as a pure gut feeling I don't think the code supports arbitrary deep brackets.

Finally, while I do think this can be valid use of "evil" eval, you do put a lot of (necessary) work into validating and replacing in order to extend the syntax and use it safely. However I believe it wouldn't be much more work to instead write a full parser/interpreter that avoids using eval.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ It has to return the error to allow giving the error at your own way. For example: alert(calculate("foo*a",1)) alerts ReferenceError: Cannot read property "foo" of "Math" \$\endgroup\$ – Migats21 Feb 4 at 9:29
  • \$\begingroup\$ You should ask me why I used while (typeof text === "string"). I think while (true) is also a strange looking code. So the question is: what is the alternate way. \$\endgroup\$ – Migats21 Feb 7 at 7:59
  • \$\begingroup\$ while (true) is certainly better than using an irrelevant expression. I spent quite a while trying to figure out when the expression turns false. There are several ways to avoid while (true), that easily can be googled. \$\endgroup\$ – RoToRa Feb 12 at 9:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.