RosettaCodeData/Task/Arithmetic-evaluation/D/arithmetic-evaluation.d

222 lines
6.2 KiB
D
Raw Permalink Normal View History

2013-04-10 16:57:12 -07:00
import std.stdio, std.string, std.ascii, std.conv, std.array,
std.exception, std.traits;
struct Stack(T) {
T[] data;
alias data this;
2015-02-20 00:35:01 -05:00
void push(T top) pure nothrow @safe { data ~= top; }
2013-04-10 16:57:12 -07:00
2015-02-20 00:35:01 -05:00
T pop(bool discard = true)() pure @nogc @safe {
immutable static exc = new immutable(Exception)("Stack Empty");
2013-04-10 16:57:12 -07:00
if (data.empty)
2015-02-20 00:35:01 -05:00
throw exc;
auto top = data[$ - 1];
2013-04-10 16:57:12 -07:00
static if (discard)
2013-10-27 22:24:23 +00:00
data.popBack;
2013-04-10 16:57:12 -07:00
return top;
}
}
enum Type { Num, OBkt, CBkt, Add, Sub, Mul, Div }
immutable opChar = ["#", "(", ")", "+", "-", "*", "/"];
immutable opPrec = [ 0, -9, -9, 1, 1, 2, 2];
2015-02-20 00:35:01 -05:00
abstract class Visitor { void visit(XP e) pure @safe; }
2013-04-10 16:57:12 -07:00
final class XP {
immutable Type type;
immutable string str;
immutable int pos; // Optional, to dispaly AST struct.
XP LHS, RHS;
2015-02-20 00:35:01 -05:00
this(string s=")", int p = -1) pure nothrow @safe {
2013-04-10 16:57:12 -07:00
str = s;
pos = p;
2014-01-17 05:32:22 +00:00
auto localType = Type.Num;
2013-04-10 16:57:12 -07:00
foreach_reverse (immutable t; [EnumMembers!Type[1 .. $]])
if (opChar[t] == s)
2014-01-17 05:32:22 +00:00
localType = t;
this.type = localType;
2013-04-10 16:57:12 -07:00
}
2015-02-20 00:35:01 -05:00
override int opCmp(Object other) pure @safe {
2013-04-10 16:57:12 -07:00
auto rhs = cast(XP)other;
enforce(rhs !is null);
return opPrec[type] - opPrec[rhs.type];
}
2015-02-20 00:35:01 -05:00
void accept(Visitor v) pure @safe { v.visit(this); }
2013-04-10 16:57:12 -07:00
}
final class AST {
XP root;
Stack!XP opr, num;
string xpr, token;
int xpHead, xpTail;
2015-02-20 00:35:01 -05:00
void joinXP(XP x) pure @safe {
2013-10-27 22:24:23 +00:00
x.RHS = num.pop;
x.LHS = num.pop;
2013-04-10 16:57:12 -07:00
num.push(x);
}
2015-02-20 00:35:01 -05:00
string nextToken() pure @safe {
2013-04-10 16:57:12 -07:00
while (xpHead < xpr.length && xpr[xpHead] == ' ')
xpHead++; // Skip spc.
xpTail = xpHead;
if (xpHead < xpr.length) {
token = xpr[xpTail .. xpTail + 1];
switch (token) {
case "(", ")", "+", "-", "*", "/": // Valid non-number.
xpTail++;
return token;
default: // Should be number.
2013-10-27 22:24:23 +00:00
if (token[0].isDigit) {
2013-04-10 16:57:12 -07:00
while (xpTail < xpr.length && xpr[xpTail].isDigit())
xpTail++;
return xpr[xpHead .. xpTail];
} // Else may be error.
} // End switch.
}
if (xpTail < xpr.length)
throw new Exception("Invalid Char <" ~ xpr[xpTail] ~ ">");
return null;
} // End nextToken.
2015-02-20 00:35:01 -05:00
AST parse(in string s) /*@safe*/ {
2013-04-10 16:57:12 -07:00
bool expectingOP;
xpr = s;
try {
xpHead = xpTail = 0;
num = opr = null;
root = null;
opr.push(new XP); // CBkt, prevent evaluate null OP precedence.
2013-10-27 22:24:23 +00:00
while ((token = nextToken) !is null) {
2013-04-10 16:57:12 -07:00
XP tokenXP = new XP(token, xpHead);
if (expectingOP) { // Process OP-alike XP.
switch (token) {
case ")":
2013-10-27 22:24:23 +00:00
while (opr.pop!false.type != Type.OBkt)
joinXP(opr.pop);
opr.pop;
2013-04-10 16:57:12 -07:00
expectingOP = true;
break;
case "+", "-", "*", "/":
2013-10-27 22:24:23 +00:00
while (tokenXP <= opr.pop!false)
2013-04-10 16:57:12 -07:00
joinXP(opr.pop());
opr.push(tokenXP);
expectingOP = false;
break;
default:
throw new Exception("Expecting Operator or ), not <"
~ token ~ ">");
}
} else { // Process Num-alike XP.
switch (token) {
case "+", "-", "*", "/", ")":
throw new Exception("Expecting Number or (, not <"
~ token ~ ">");
case "(":
opr.push(tokenXP);
expectingOP = false;
break;
default: // Number.
num.push(tokenXP);
expectingOP = true;
}
}
xpHead = xpTail;
} // End while.
while (opr.length > 1) // Join pending Op.
2013-10-27 22:24:23 +00:00
joinXP(opr.pop);
2013-04-10 16:57:12 -07:00
} catch(Exception e) {
writefln("%s\n%s\n%s^", e.msg, xpr, " ".replicate(xpHead));
root = null;
return this;
}
if (num.length != 1) { // Should be one XP left.
2013-10-27 22:24:23 +00:00
"Parse Error...".writefln;
2013-04-10 16:57:12 -07:00
root = null;
} else {
2013-10-27 22:24:23 +00:00
root = num.pop;
2013-04-10 16:57:12 -07:00
}
return this;
} // End Parse.
} // End class AST.
// To display AST fancy struct.
void ins(ref char[][] s, in string v, in int p, in int l)
2015-02-20 00:35:01 -05:00
pure nothrow @safe {
2013-04-10 16:57:12 -07:00
if (l + 1 > s.length)
s.length++;
while (s[l].length < p + v.length + 1)
s[l] ~= " ";
2013-10-27 22:24:23 +00:00
s[l][p .. p + v.length] = v[];
2013-04-10 16:57:12 -07:00
}
final class CalcVis : Visitor {
int result, level;
string resultStr;
char[][] Tree;
2015-02-20 00:35:01 -05:00
static void opCall(AST a) @safe {
2013-04-10 16:57:12 -07:00
if (a && a.root) {
auto c = new CalcVis;
a.root.accept(c);
foreach (immutable i; 1 .. c.Tree.length) { // More fancy.
bool flipflop = false;
enum char mk = '.';
foreach (immutable j; 0 .. c.Tree[i].length) {
while (j >= c.Tree[i - 1].length)
c.Tree[i - 1] ~= " ";
immutable c1 = c.Tree[i][j];
immutable c2 = c.Tree[i - 1][j];
if (flipflop && (c1 == ' ') && c2 == ' ')
c.Tree[i - 1][j] = mk;
if (c1 != mk && c1 != ' ' &&
(j == 0 || !isDigit(c.Tree[i][j - 1])))
flipflop = !flipflop;
}
}
foreach (const t; c.Tree)
2013-10-27 22:24:23 +00:00
t.writefln;
2013-04-10 16:57:12 -07:00
writefln("\n%s ==>\n%s = %s", a.xpr, c.resultStr, c.result);
} else
2013-10-27 22:24:23 +00:00
"Evalute invalid or null Expression.".writefln;
2013-04-10 16:57:12 -07:00
}
// Calc. the value, display AST struct and eval order.
2015-02-20 00:35:01 -05:00
override void visit(XP xp) @safe {
2013-04-10 16:57:12 -07:00
ins(Tree, xp.str, xp.pos, level);
level++;
if (xp.type == Type.Num) {
resultStr ~= xp.str;
2013-10-27 22:24:23 +00:00
result = xp.str.to!int;
2013-04-10 16:57:12 -07:00
} else {
resultStr ~= "(";
xp.LHS.accept(this);
immutable int lhs = result;
resultStr ~= opChar[xp.type];
xp.RHS.accept(this);
resultStr ~= ")";
switch (xp.type) {
case Type.Add: result = lhs + result; break;
case Type.Sub: result = lhs - result; break;
case Type.Mul: result = lhs * result; break;
case Type.Div: result = lhs / result; break;
default: throw new Exception("Invalid type");
}
}
level--;
}
}
2015-02-20 00:35:01 -05:00
void main(string[] args) /*@safe*/ {
2013-04-10 16:57:12 -07:00
immutable exp0 = "1 + 2*(3 - 2*(3 - 2)*((2 - 4)*5" ~
" - 22/(7 + 2*(3 - 1)) - 1)) + 1";
2015-02-20 00:35:01 -05:00
immutable exp = (args.length > 1) ? args[1 .. $].join(' ') : exp0;
2013-10-27 22:24:23 +00:00
new AST().parse(exp).CalcVis; // Should be 60.
2013-04-10 16:57:12 -07:00
}