PDA

View Full Version : Need help converting string to formula

Falkon303
02-15-2009, 09:44 PM
Ok, so I have a page where I can input mathematical formulas -

ie: a text input with the value = "(((3+5)*8)/1.4)".

What I am trying to do is convert that string to an actual answer.

Given, in javascript I can write out the equation internally, but how to convert a string to utilize javascripts internal functionality?

I realize I could assign variables to each mathematical character and split those chars in a string length -

r = );

but isn't there an easier way?

any help much appreciated.

- Ben

Twey
02-15-2009, 10:08 PM
You've found one of the few places in which use of eval() is justified. Just check for unwanted characters first:

var Calculator = (function() {
function safe(expr) {
if (/[^\d*-+\/()^%. ]/.test(expr))
throw new Exception("Malformed expression.");

return expr;
}

function calculate(expr) {
try {
return eval(safe(expr));
} catch (e) {
}
}

return { calculate: calculate };
})();

Falkon303
02-16-2009, 07:20 AM
You sir, are a gentleman and a scholar! Thanks much! :)

jscheuer1
02-16-2009, 08:07 AM

if (/^[0-9*-+\/()^%]/.test(expr))

should be:

if (/[^(-9 ]|,|([\d ]\.\d+\.)|(\D\.\D)/.test(expr))

And the whole thing is overly complex:

var Calculator = {
safe: function(expr) {
if (/[^(-9 ]|,|([\d ]\.\d+\.)|(\D\.\D)/.test(expr))
else
return expr;
return undefined;
},
calculate: function(expr) {
return eval(this.safe(expr));
}
};

Twey
02-16-2009, 03:30 PM
if (/^[0-9*-+\/()^%]/.test(expr)) should be:
if (/[^(-9 ]|,|([\d ]\.\d+\.)|(\D\.\D)/.test(expr))And the whole thing is overly complex:I think you missed the point a little. safe() is just for checking that there aren't any weird function calls in the expression (although I did mean [^ and not ^[, I really do need the decimal point in there, and \d is nicer than 0-9). We catch any otherwise-malformed or impossible calculations later, in the catch block; safe() throws an exception to unify the error handling there.

You can save three lines by doing it your way, but at the cost of names and precompilation, exposing safe() which the user doesn't really need to know about, and requiring the context to be the global object, which makes it awkward to use as a first-class function. I'd rather stick with the original.

jscheuer1
02-16-2009, 04:54 PM
Hmm. Let's see:

[^(-9 ]|,

or what as an alternative? The above takes in:

()*+-./0123456789

and space as the 'allowed' (via negativa) characters.

I don't quite get the bit about exposure, safe() is accessible (in my version), but not really exposed, it can only be accessed through the already exposed object, which is exposed in both our versions.

Twey
02-16-2009, 05:18 PM
It does, although I think the intention is somewhat less clear. I was talking more about the rest of the pattern, where you do further regex validation, and the removal of the try/catch block, whereby you fail to catch patterns that contain only valid characters but are nevertheless not valid for whatever reason.

I don't quite get the bit about exposure, safe() is accessible (in my version), but not really exposed, it can only be accessed through the already exposed object, which is exposed in both our versions.In programming terms, we refer to something accessible to the user as 'exposed' (or 'public'). This isn't a big deal in JS, since neither the language nor its philosophy really encourage it, but encapsulating methods that don't need to be exposed means one can rely on the fact that they're never going to be used by users of your code, and therefore feel free to change the internal implementation as desired, while a public API should generally remain stable for fear of breaking existing code. In our code, safe() is an implementation detail: it may be replaced, and it's not designed to be used by external code — the fact that the check is incomplete makes it pretty much worthless for any usage except our own, which later does more extensive checking at evaluation-time by means of the try/catch block. If we had a more reliable version of safe() then it might be worthwhile exposing part of it as a boolean isSafe() or something, but I think that evaluating the code twice in order to perform validation would be clumsy in the extreme. If we did desire more flexibility, we would remove the try/catch block and allow the calling code to handle any exceptions that might occur.

jscheuer1
02-16-2009, 07:24 PM
OK, then:

var Calculator = (function(){
var safe = function(expr){
if (/[^(-9 ]|,/.test(expr))
throw('');
return expr;
};
return {calculate: function(expr){
try {
return eval(safe(expr));
}
};
})();

Twey
02-16-2009, 08:19 PM
That's nice, although you're still losing precompilation and names, and if you put the formatting back* it's the same size as mine :p

*
var Calculator = (function() { var Calculator = (function() {
var safe = function(expr) { function safe(expr) {
if (/[^(-9 ]|,/.test(expr)) if (/[^\d*-+\/()^%. ]/.test(expr))
throw(''); throw "Malformed expression.";

return expr; return expr;
}; }

return { function calculate(expr) {
calculate: function(expr) { try {
try { return eval(safe(expr));
return eval(safe(expr)); } catch (e) {
} catch (e) { alert("Malformed expression.");
} }
}
}; return { calculate: calculate };
})(); })();

jscheuer1
02-16-2009, 09:05 PM
I'm not sure what you mean by precompilation or names. By the time Calculator.calculate() is used, it is compiled. What names are available in your version that aren't available in mine?

Of course, I could be missing something. This is the first time I think I really understood public and private, so I'm certainly not opposed to learning more.

Twey
02-17-2009, 02:33 AM
I'm not sure what you mean by precompilation or names. By the time Calculator.calculate() is used, it is compiled. What names are available in your version that aren't available in mine?There are two main differences between function foo() { } and var foo = function() { };. The first is one of precompilation. JS is parsed in two passes: firstly comes an optimising pass, where things that can be are parsed and compiled into native form where possible; then comes the main evaluation pass, from the top down, which actually evaluates the code. Function statements (function foo() { }) are compiled during that initial phase; function expressions (var foo = function() { };) are not. The other difference is that the function statement binds a name to the created function, accessible with foo.name, which can be used by the function to refer to itself even if it goes out of its own scope, and which is reported in error messages and the like to aid debugging. It is possible to do this with function expressions as well (var foo = function foo() { }), but I recall reading somewhere that it's terribly slow in Spidermonkey.

jscheuer1
02-17-2009, 04:30 AM
I see what you mean about compilation and two passes. But in the current example, regardless of how it is done (as far as the two versions under discussion here go), I think that the second pass has already occurred before a subsequent evaluation occurs, where function is actually used. So are you saying that the less left to the second pass, the sooner or faster the evaluation phase will be when it comes time to execute?

I'm willing to go with first pass as best though, if I get more confirmation, and would tend to think you know what you are talking about here anyway.

I'm still at a loss about the names. Calculator.calculate exists in both our most recent versions. Calculator.safe does not, and the safe() function is available privately to Calculator.calculate - so I fail to see the difference you are are trying to explain as regards names, though as I say, I may be missing something.

Further, and not directly related as far as I can tell - it certainly would be more efficient simply from a code writing standpoint to use the:

return {calculate: calculate};

construct if more than one function were desired as a public method.

Twey
02-17-2009, 04:58 PM
But in the current example, regardless of how it is done (as far as the two versions under discussion here go), I think that the second pass has already occurred before a subsequent evaluation occurs, where function is actually used. So are you saying that the less left to the second pass, the sooner or faster the evaluation phase will be when it comes time to execute?

I will have to admit that I'm on shaky ground here, but as far as I understand it: yes, the first pass is only a shallow optimisation pass, and therefore takes only a little extra time but produces considerable overall savings in the evaluation phase.

I'm still at a loss about the names. Calculator.calculate exists in both our most recent versions. Calculator.safe does not, and the safe() function is available privately to Calculator.calculate - so I fail to see the difference you are are trying to explain as regards names, though as I say, I may be missing something.

You are indeed. What I'm trying to explain is that there is another name associated with functions, completely independent of the variables or properties to which it may be assigned. For example, using the slow named function expressions for examplary purposes:
var foo = function bar() { bar(); };In this code, no variable bar is created, yet code within our function can refer to itself by that name — we've created a stack overflow, and when that stack overflow occurs, the name reported in the error message will be bar. If we hadn't named it, no name could be given to aid debugging, since the same function can be assigned to multiple variables — the interpreter wouldn't know which one to report.