そうですね、ある時点で、どの操作をサポートするかを正確に決定する必要があります。その後、 Shunting-yard アルゴリズムのようなパーサーを使用して、より評価しやすい方程式の表現 (つまり、抽象構文ツリー) を生成するエバリュエーターを実装することは難しくありません。
JavaScript で書かれたこの種のエバリュエーターの簡単な例があります: http://gjp.cc/projects/logic_tables.html LaTeX の代わりに論理式を!(p ^^ q) & ~(p || q)
使用しますが、それでも有用な例になる可能性があります。
JavaScript ( http://gpittarelli.com/projects/logic_tables.js ):
var CALCULATOR_CONSTANTS = {
/* True values. */
't': true,
'true': true,
/* False values. */
'c': false,
'false': false
};
// The Calculator constructor takes an expression and parses
// it into an AST (refered to as rpn_expr)
var Calculator = function(expr) {
this.valid = true;
var OpPrecedence = function(op) {
return (op === "!" || op === "~")? 9
: (op === "&" || op === "&&")? 7
: (op === "|" || op === "||" )? 7
: (op === "^" || op === "^^")? 7
: (op === "->")? 5
: (op === "<-")? 5
: 0;
}
var OpAssociativity = function(op) {
return (op === "!" || op === "~")? "R":"L";
}
this.rpn_expr = [];
this.variables = [];
var rpn_expr = this.rpn_expr;
var variables = this.variables;
expr = expr.replace(/\s+/g, "");
// This nice long regex matches any valid token in a user
// supplied expression (e.g. an operator, a constant or
// a variable)
var in_tokens = expr.match(/(\!|\~|\|+|&+|\(|\)|\^+|(->)|(<-)|[a-zA-Z0-9]+)/gi);
var op_stack = [];
in_tokens.forEach(function(token) {
if (/[a-zA-Z0-9]+/.test(token)) {
if (CALCULATOR_CONSTANTS.hasOwnProperty(token)) {
// Constant. Pushes a boolean value onto the stack.
rpn_expr.push(CALCULATOR_CONSTANTS[token]);
} else {
// Variables
rpn_expr.push(token);
variables.push(token);
}
}
else if (token === ")") {
// Pop tokens off the op_stack onto the rpn_expr until we
// reach the matching (
while (op_stack[op_stack.length-1] !== "(") {
rpn_expr.push(op_stack.pop());
if (op_stack.length === 0) {
this.valid = false;
return;
}
}
// Remove the (
op_stack.pop();
}
else if (token === "(") {
op_stack.push(token);
}
else {
// Operator
var tokPrec = OpPrecedence( token ),
headPrec = OpPrecedence( op_stack[op_stack.length-1] );
while ((OpAssociativity(token) === "L" && tokPrec <= headPrec)
|| (OpAssociativity(token) === "R" && tokPrec < headPrec) ) {
rpn_expr.push(op_stack.pop());
if (op_stack.length === 0)
break;
headPrec = OpPrecedence( op_stack[op_stack.length-1] );
}
op_stack.push(token);
}
});
// Push all remaining operators onto the final expression
while (op_stack.length > 0) {
var popped = op_stack.pop();
if (popped === ")") {
this.valid = false;
break;
}
rpn_expr.push(popped);
}
this.optimize();
}
/** Returns the variables used in the currently loaded expression. */
Calculator.prototype.getVariables = function() { return this.variables; }
Calculator.prototype.optimize = function() {
// Single-pass optimization, mainly just to show the concept.
// Looks for statements that can be pre computed, eg:
// p | true
// q & false
// r ^ r
// etc...
// We do this by reading through the RPN expression as if we were
// evaluating it, except instead rebuild it as we go.
var stack = [], rpn_expr = this.rpn_expr;
rpn_expr.forEach(function(token) {
if (typeof token === "boolean") {
// Constant.
stack.push(token);
} else if (/[a-zA-Z0-9]+/.test(token)) {
// Identifier - push onto the stack
stack.push(token);
} else {
// Operator - The actual optimization takes place here.
// TODO: Add optimizations for more operators.
if (token === "^" || token === "^^") {
var a = stack.pop(), b = stack.pop();
if (a === b) { // p ^ p == false
stack.push(false);
} else {
stack.push(b);
stack.push(a);
stack.push(token);
}
} else if (token === "|" || token === "||") {
var a = stack.pop(), b = stack.pop();
if (a === true || b === true) {
// If either of the operands is a tautology, OR is
// also a tautology.
stack.push(true);
} else if (a === b) { // p | p == p
stack.push(a);
} else {
stack.push(b);
stack.push(a);
stack.push(token);
}
} else if (token === "!" || token === "~") {
var p = stack.pop();
if (typeof p === "boolean") {
// NOT of a constant value can always
// be precalculated.
stack.push(!p);
} else {
stack.push(p);
stack.push(token);
}
} else {
stack.push(token);
}
}
});
this.rpn_expr = stack;
}
/**
* returns the result of evaluating the current expressions
* with the passed in <code>variables</code> object. <i>variables</i>
* should be an object who properties map from key => value
*/
Calculator.prototype.eval = function(variables) {
var stack = [], rpn_expr = this.rpn_expr;
rpn_expr.forEach(function(token) {
if (typeof token === "boolean") {
// Constant.
stack.push(token);
} else if (/[a-zA-Z0-9]+/.test(token)) {
// Identifier - push its boolean value onto the stack
stack.push(!!variables[token]);
} else {
// Operator
var q = stack.pop(), p = stack.pop();
if (token === "^" || token === "^^") {
stack.push((p? 1:0) ^ (q? 1:0));
} else if (token === "|" || token === "||") {
stack.push(p || q);
} else if (token === "&" || token === "&&") {
stack.push(p && q);
} else if (token === "!" || token === "~") {
stack.push(p);
stack.push(!q);
} else if (token === "->") {
stack.push((!p) || q);
} else if (token === "<-") {
stack.push((!q) || p);
}
}
});
return stack.pop()? 1:0;
};