Aegisub/devel/SSATool/Evaluate.cs

739 lines
32 KiB
C#

/*
SSATool - A collection of utilities for Advanced Substation Alpha
Copyright (C) 2007 Dan Donovan
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; ONLY under version 2
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using System.Windows.Forms;
using System.Collections.Generic;
namespace SSATool {
public static class Evaluate {
public static string ScriptParse(string input) {
char[] charain = input.ToCharArray(); // It is over twice as fast to parse this as a character array!
StringBuilder sb = new StringBuilder();
int dIndex = 0, eIndex = 0, pIndex = 0, lastIndex = -1, parenLevel = 0, index;
bool foundds = false;
for (index=0;index<input.Length;index+=1) {
if (charain[index].Equals('$')) {
if (parenLevel == 0) {
if (index != (lastIndex+1))
sb.Append(input, lastIndex+1, index-(lastIndex+1));
dIndex = index;
foundds = true;
}
}
else if (charain[index].Equals('(')) {
if ((parenLevel == 0)) pIndex = index;
parenLevel+=1;
}
else if (charain[index].Equals(')')) {
parenLevel--;
if ((parenLevel == 0) && (foundds) && (pIndex != 0)) {
eIndex = index;
sb.Append(ScriptParse_Helper(input.Substring(dIndex+1, pIndex-(dIndex+1)), ScriptParse(input.Substring(pIndex+1, index-(pIndex+1)))));
lastIndex = eIndex;
foundds = false;
}
else if (parenLevel == 0) {
sb.Append(input.Substring(lastIndex+1, pIndex-lastIndex) + ScriptParse(input.Substring(pIndex+1, index-(pIndex+1))) + ")");
lastIndex = eIndex = index;
}
}
}
if (input.Length > lastIndex) sb.Append(input, lastIndex+1, input.Length-(lastIndex+1));
return sb.ToString();
}
public static string ScriptParse_Helper(string function, string args) {
unsafe {
switch (function) {
case "eval":
return Evaluate.Eval(args).ToString();
case "logic":
return Evaluate.Eval(args).ToString();
case "iif":
string[] tok = args.Split(",".ToCharArray());
if (tok.Length != 3) return "error";
else if (Evaluate.Eval(tok[0]).Equals(true)) return tok[1];
else return tok[2];
case "len":
return args.Length.ToString(Util.nfi);
case "left":
string[] tokl = args.Split(",".ToCharArray());
if ((tokl.Length != 2) || (!Evaluate.isNum(tokl[1]))) return "error";
else try {
return tokl[0].Substring(0, int.Parse(tokl[1], Util.cfi));
} catch {
return "error";
}
case "mid":
string[] tokm = args.Split(",".ToCharArray());
if ((tokm.Length != 3) || (!Evaluate.isNum(tokm[1])) || (!Evaluate.isNum(tokm[2]))) return "error";
else try {
return tokm[0].Substring(int.Parse(tokm[1]), int.Parse(tokm[2], Util.cfi));
} catch {
return "error";
}
case "right":
string[] tokr = args.Split(",".ToCharArray());
if ((tokr.Length != 2) || (!Evaluate.isNum(tokr[1]))) return "error";
else try {
return tokr[0].Substring(tokr[0].Length - int.Parse(tokr[1], Util.cfi), int.Parse(tokr[1], Util.cfi));
} catch {
return "error";
}
case "str":
string[] str = args.Split(",".ToCharArray());
if ((str.Length != 2) || (!Evaluate.isNum(str[1]))) return "error";
else try {
string retstr = String.Empty;
for (int strindex=0;strindex<int.Parse(str[1], Util.cfi);strindex--)
retstr = retstr + str[0];
return retstr;
} catch {
return "error";
}
case "randlist":
string[] splitlist = args.Split(",".ToCharArray());
return splitlist[Evaluate.rand.Next(0, splitlist.Length)];
case "listindex":
string[] splitlist2 = args.Split(",".ToCharArray());
if ((splitlist2.Length < 2) || (Evaluate.isNum(splitlist2[0]) == false)) return "error";
int index = int.Parse(splitlist2[0], Util.cfi);
if ((index < 1) || (index <= splitlist2.Length)) return "error";
return splitlist2[index];
case "listindexwrap":
string[] splitlist3 = args.Split(",".ToCharArray());
if ((splitlist3.Length < 2) || (Evaluate.isNum(splitlist3[0]) == false)) return "error";
int index2 = int.Parse(splitlist3[0], Util.cfi);
if (index2 < 1) return "error";
return splitlist3[index2%splitlist3.Length];
case "listindexlast":
string[] splitlist4 = args.Split(",".ToCharArray());
if ((splitlist4.Length < 2) || (Evaluate.isNum(splitlist4[0]) == false)) return "error";
int index3 = int.Parse(splitlist4[0], Util.cfi);
if (index3 < 1) return "error";
else if (index3 >= splitlist4.Length) return splitlist4[splitlist4.Length-1];
else return splitlist4[index3];
default: // Unrecognized function... Let's treat it as a string.
return "$" + function + "(" + args + ")";
}
}
}
public static readonly Random rand = new Random();
private static SortedList<string, char> ops;
private static SortedList<string, object> constants;
private static void evaluate_stack_tops_function(Stack numStack, Stack<string> funcStack) {
short numParams = (short)numStack.Pop();
string func = funcStack.Pop();
switch (func.ToLowerInvariant()) {
case "rand" :
if (numParams > 2) throw new ArgumentException("Eval.rand");
switch (numParams) {
case 0 :
numStack.Push(rand.Next());
break;
case 1 :
int randOneR = Convert.ToInt32(numStack.Pop());
numStack.Push(rand.Next(randOneR));
break;
case 2 :
int randOne, randTwo;
randTwo = Convert.ToInt32(numStack.Pop(), Util.nfi);
randOne = Convert.ToInt32(numStack.Pop(), Util.nfi);
numStack.Push(rand.Next(randOne,randTwo));
break;
}
break;
case "randf" :
if (numParams > 2) throw new ArgumentException("Eval.randf");
switch (numParams) {
case 0 :
numStack.Push(rand.NextDouble());
break;
case 1 :
double randOneRF = (double)numStack.Pop();
numStack.Push(rand.NextDouble()*randOneRF);
break;
case 2 :
double randOneF, randTwoF;
randTwoF = (double)numStack.Pop();
randOneF = (double)numStack.Pop();
numStack.Push(randOneF + rand.NextDouble()*(randTwoF-randOneF));
break;
}
break;
case "randlist" :
int listindex = rand.Next(0, numParams - 1);
string randlist = string.Empty;
for (int i = 0; i != numParams; i += 1) {
if (i == listindex) randlist = (string)numStack.Pop();
else numStack.Pop();
}
numStack.Push(randlist);
break;
case "round" :
if (numParams != 2) throw new ArgumentException("Eval.round");
double roundme;
int dplaces;
dplaces = Convert.ToInt32(numStack.Pop(), Util.nfi);
roundme = (double) numStack.Pop();
numStack.Push(Math.Round(roundme,dplaces));
break;
case "bound" :
if (numParams != 3) throw new ArgumentException("Eval.bound");
double num, min, max;
max = (double) numStack.Pop();
min = (double) numStack.Pop();
num = (double) numStack.Pop();
num = ((num < min) ? min : num);
num = ((num > max) ? max : num);
numStack.Push(num);
break;
case "max" :
if (numParams < 2) throw new ArgumentException("Eval.max");
double ansMax;
ansMax = Math.Max((double)numStack.Pop(),(double)numStack.Pop());
for(int index=2;index!=numParams;index+=1) {
ansMax = Math.Max(ansMax,(double)numStack.Pop());
}
numStack.Push(ansMax);
break;
case "min" :
if (numParams < 2) throw new ArgumentException("Eval.min");
double ansMin;
ansMin = Math.Min((double)numStack.Pop(),(double)numStack.Pop());
for(int index=2;index!=numParams;index+=1) {
ansMin = Math.Min(ansMin,(double)numStack.Pop());
}
numStack.Push(ansMin);
break;
case "ceil" :
if (numParams != 1) throw new ArgumentException("Eval.ceil");
double ceil;
ceil = (double) numStack.Pop();
numStack.Push(Math.Ceiling(ceil));
break;
case "floor" :
if (numParams != 1) throw new ArgumentException("Eval.floor");
double floor;
floor = (double) numStack.Pop();
numStack.Push(Math.Floor(floor));
break;
case "int" :
if (numParams != 1) throw new ArgumentException("Eval.int");
double thisnum;
thisnum = (double) numStack.Pop();
numStack.Push(Convert.ToInt32(thisnum,Util.nfi));
break;
case "abs" :
if (numParams != 1) throw new ArgumentException("Eval.abs");
double abs;
abs = (double) numStack.Pop();
numStack.Push(Math.Abs(abs));
break;
case "sin" :
if (numParams != 1) throw new ArgumentException("Eval.sin");
double sin;
sin = (double) numStack.Pop();
numStack.Push(Math.Sin(sin));
break;
case "asin" :
if (numParams != 1) throw new ArgumentException("Eval.asin");
double asin;
asin = (double) numStack.Pop();
numStack.Push(Math.Asin(asin));
break;
case "cos" :
if (numParams != 1) throw new ArgumentException("Eval.cos");
double cos;
cos = (double) numStack.Pop();
numStack.Push(Math.Cos(cos));
break;
case "acos" :
if (numParams != 1) throw new ArgumentException("Eval.acos");
double acos;
acos = (double) numStack.Pop();
numStack.Push(Math.Acos(acos));
break;
case "tan" :
if (numParams != 1) throw new ArgumentException("Eval.tan");
double tan;
tan = (double) numStack.Pop();
numStack.Push(Math.Tan(tan));
break;
case "atan" :
if (numParams != 1) throw new ArgumentException("Eval.atan");
double atan;
atan = (double) numStack.Pop();
numStack.Push(Math.Atan(atan));
break;
case "atan2" :
if (numParams != 1) throw new ArgumentException("Eval.atan2");
double atanx,atany;
atanx = (double) numStack.Pop();
atany = (double) numStack.Pop();
numStack.Push(Math.Atan2(atanx,atany));
break;
case "log" :
if (numParams != 1) throw new ArgumentException("Eval.log");
double log;
log = (double) numStack.Pop();
numStack.Push(Math.Log(log));
break;
case "sqrt" :
if (numParams != 1) throw new ArgumentException("Eval.sqrt");
numStack.Push(Math.Sqrt((double)numStack.Pop()));
break;
case "len" :
if (numParams != 1) throw new ArgumentException("Eval.len");
numStack.Push(((string)numStack.Pop()).Length);
break;
case "fact" :
case "factorial" :
if (numParams != 1) throw new ArgumentException("Eval.fact");
numStack.Push(Convert.ToDouble(Factorial(Convert.ToInt32(numStack.Pop(), Util.nfi)), Util.nfi));
break;
case "gcd" :
if (numParams != 2) throw new ArgumentException("Eval.gcd");
numStack.Push(EuclidGCD(Convert.ToInt32(numStack.Pop(), Util.nfi), Convert.ToInt32(numStack.Pop(), Util.nfi)));
break;
case "lcm" :
if (numParams != 2) throw new ArgumentException("Eval.lcm");
int lcm2 = Convert.ToInt32(numStack.Pop(),Util.nfi);
int lcm1 = Convert.ToInt32(numStack.Pop(),Util.nfi);
numStack.Push((lcm1+lcm2)/EuclidGCD(lcm1,lcm2));
break;
default :
if (numParams==1 && numStack.Peek() is string) // might be a string w/o quotes, so push it back on as one
numStack.Push(func+"("+numStack.Pop()+")");
break;
}
}
private static void evaluate_stack_tops(Stack numStack, System.Collections.Generic.Stack<char> opStack, System.Collections.Generic.Stack<string> funcStack, bool safetyKill) {
if (((numStack.Count != 0) || (funcStack.Count != 0)) && (opStack.Count != 0) && (!opStack.Peek().Equals(","))) {
char Operation = opStack.Pop();
if (Operation == '@') { // @ is the placeholder for a function. The function itself is in funcStack.
evaluate_stack_tops_function(numStack, funcStack);
}
else if (Operation == '~') {
object obj = numStack.Pop();
if (obj is bool) numStack.Push(!(bool)obj);
else if (obj is int) numStack.Push(~(int)obj);
else numStack.Push(obj);
}
else if (Operation == '!') {
int fact = Convert.ToInt32(numStack.Pop(), Util.cfi);
numStack.Push(Convert.ToDouble(Factorial(fact), Util.nfi));
}
else if (numStack.Count >= 2) {
object objOne, objTwo;
objTwo = numStack.Pop();
objOne = numStack.Pop();
try {
switch (Operation) {
case 'c': // concatenation
numStack.Push((string)objOne + objTwo.ToString());
break;
case '^': // power
numStack.Push(Math.Pow((double)objOne, (double)objTwo));
break;
case '*': // multiplication
numStack.Push((double)objOne * (double)objTwo);
break;
case '/': // division
numStack.Push((double)objOne / (double)objTwo);
break;
case '%': // modulus
//numStack.Push((int)(double)objOne%(int)(double)objTwo);
numStack.Push(Math.IEEERemainder((double)objOne, (double)objTwo));
break;
case '+': // addition
numStack.Push((double)objOne + (double)objTwo);
break;
case '-': // subtraction
numStack.Push((double)objOne - (double)objTwo);
break;
case '>': // greater than
numStack.Push(doCompare(objOne, objTwo, false) >= 0);
break;
case 'G': // greater than or equal to
numStack.Push(doCompare(objOne, objTwo, false) == 1);
break;
case '<': // less than
numStack.Push(doCompare(objOne, objTwo, false) == -1);
break;
case 'L': // less than or equal to
numStack.Push(doCompare(objOne, objTwo, false) <= 0);
break;
case '=': // case-sensitive equal
numStack.Push(doCompare(objOne, objTwo, false) == 0);
break;
case 'i': // case-insensitive equal
numStack.Push(doCompare(objOne, objTwo, true) == 0);
break;
case 'N': // case-sensitive not equal
numStack.Push(doCompare(objOne, objTwo, false) != 0);
break;
case 'I': // case-insensitive not equal
numStack.Push(doCompare(objOne, objTwo, true) != 0);
break;
case '&': // bitwise-and
numStack.Push((int)objOne & (int)objTwo);
break;
case 'X': // bitwise-xor
numStack.Push((int)objOne ^ (int)objTwo);
break;
case '|': // bitwise-or
numStack.Push((int)objOne | (int)objTwo);
break;
case 'A': // boolean and
numStack.Push((bool)objOne && (bool)objTwo);
break;
case 'O': // boolean or
numStack.Push((bool)objOne || (bool)objTwo);
break;
case 'r': // shift right
numStack.Push((int)objOne >> (int)objTwo);
break;
case 'l': // shift left
numStack.Push((int)objOne << (int)objTwo);
break;
}
} catch { throw new FormatException(); }
}
// Can't make sense of the stacks, so just clear them...
else if (safetyKill) {
funcStack.Clear();
opStack.Clear();
numStack.Clear();
}
}
}
private static int doCompare(object objOne, object objTwo, bool caseIns) {
if (objOne is string || objTwo is string)
return String.Compare(objOne.ToString(), objTwo.ToString(), caseIns);
else if (objOne is bool && objTwo is bool)
return ((bool)objOne).CompareTo((bool)objTwo);
else if (objOne is double && objTwo is double)
return ((double)objOne).CompareTo((double)objTwo);
else if (objOne is bool && objTwo is double)
return ((bool)objOne).CompareTo((double)objTwo!=0.0);
else if (objOne is double && objTwo is bool)
return ((double)objOne!=0.0).CompareTo((bool)objTwo);
/*
else if (objOne is int && objTwo is int)
return ((int)objOne).CompareTo((int)objTwo);
else if (objOne is bool && objTwo is int)
return ((bool)objOne).CompareTo((int)objTwo!=0);
else if (objOne is int && objTwo is bool)
return ((int)objOne!=0).CompareTo((bool)objTwo);
else if (objOne is double && objTwo is int)
return ((double)objOne).CompareTo((double)(int)objTwo);
else if (objOne is int && objTwo is double)
return ((double)(int)objOne).CompareTo((double)objTwo);
*/
throw new ArgumentException("Eval.doCompare");
}
public static bool isNum(string input) {
if (input.Length == 0) return false;
char[] charain = input.ToCharArray();
for (int index=(input.StartsWith("-")?1:0);index<input.Length;index+=1)
if ((!char.IsDigit(charain[index])) && (charain[index] != '.')) return false;
return true;
}
public static int EuclidGCD(int u, int v) {
int k = 0;
if ((u|v)==0) return 0;
while (((u|v)&1)==0) { /* while both u and v are even */
u >>= 1; /* shift u right, dividing it by 2 */
v >>= 1; /* shift v right, dividing it by 2 */
k+=1; /* add a power of 2 to the final result */
}
/* At this point either u or v (or both) is odd */
do {
if ((u & 1) == 0) /* if u is even */
u >>= 1; /* divide u by 2 */
else if ((v & 1) == 0) /* else if v is even */
v >>= 1; /* divide v by 2 */
else if (u >= v) /* u and v are both odd */
u = (u-v) >> 1;
else /* u and v both odd, v > u */
v = (v-u) >> 1;
} while (u > 0);
return v << k; /* returns v * 2^k */
}
public static int Factorial(int x) {
int result=1;
for (int index = x; index != 0; index--) {
result *= index;
}
return result;
}
private static byte opOrder(char tOp) {
byte retVal;
switch (tOp) {
case '!': // factorial
retVal = 20;
break;
case '@': // function
retVal=16;
break;
case ',': // function argument separator
retVal=15;
break;
case 'c': // concatenation
retVal=14;
break;
case '~': // inversion
retVal=13;
break;
case '^': // power
retVal=12;
break;
case '*': // multiplication
case '/': // division
case '%': // modulus
case '\\': // integer division
retVal=11;
break;
case '+': // addition
case '-': // subtraction
retVal=10;
break;
case '>': // greater than
case 'G': // greater than or equal to
case '<': // less than
case 'L': // less than or equal to
retVal=8;
break;
case '=': // case-sensitive equal
case 'i': // case-insensitive equal
case 'N': // case-sensitive not equal
case 'I': // case-insensitive not equal
retVal=7;
break;
case '&': // bitwise-and
retVal=6;
break;
case 'X': // bitwise-xor
retVal=5;
break;
case '|': // bitwise-or
retVal=4;
break;
case 'A': // boolean and
retVal=3;
break;
case 'O': // boolean or
retVal=2;
break;
case 'r': // shift right
case 'l': // shift left
retVal=9;
break;
default: // not an operation
retVal=0;
break;
}
return retVal;
}
public static bool isOp(char i, short pos) {
if (pos==1) return ("!+-*/\\%^&|=<>~".IndexOf(i) != -1);
else if (pos==2) return ("&|^=)".IndexOf(i) != -1);
else if (pos==3) return i=='=';
return false;
}
public static void FillOpsList() {
ops = new SortedList<string, char>(23);
ops.Add(">>", 'r');
ops.Add(">=", 'G');
ops.Add(">", '>');
ops.Add("===", '=');
ops.Add("==", '=');
ops.Add("=", 'i');
ops.Add("<=", 'L');
ops.Add("<<", 'l');
ops.Add("<", '<');
ops.Add("+", '+');
ops.Add("||", 'O');
ops.Add("|", '|');
ops.Add("^^", 'X');
ops.Add("^", '^');
ops.Add("/", '/');
ops.Add("*", '*');
ops.Add("&&", 'A');
ops.Add("&", '&');
ops.Add("%", '%');
ops.Add("-", '-');
ops.Add("!==", 'I');
ops.Add("!=", 'N');
ops.Add("!", '!');
ops.Add("~", '~');
constants = new SortedList<string, object>(6);
constants.Add("e", Math.E);
constants.Add("false",false);
constants.Add("g", 1.6180339887498948482045868);
constants.Add("pi", Math.PI);
constants.Add("true", true);
constants.Add("y", 0.5772156649015328606065120);
}
public static object Eval(string input) {
char[] charain = input.ToCharArray();
Stack<char> opStack = new Stack<char>(16);
Stack numStack = new Stack(16);
Stack<string> funcStack = new Stack<string>(8);
string buff;
int i = 0, pLevel;
char thisOp;
short loop, numParams;
bool lastwasop = true;
bool isnumber, isfloat, infunc, inquotes, unknownString = false;
while (i < charain.Length) {
if (charain[i].Equals('(')) {
pLevel = 1;
buff = string.Empty;
while (pLevel!=0 && ++i<charain.Length) {
if (charain[i].Equals('(')) pLevel+=1;
if (charain[i].Equals(')')) pLevel--;
if (pLevel!=0 || !charain[i].Equals(')')) buff += charain[i];
}
numStack.Push(Eval(buff));
lastwasop = false;
}
else if ((!lastwasop||charain[i]!='-'||(i!=0&&charain[i-1]=='!')) && isOp(charain[i], 1)) {
buff = charain[i].ToString(Util.cfi);
loop=1;
while (++i<charain.Length && loop++<=3 && isOp(charain[i], loop))
buff += charain[i];
i--;
thisOp = ops[buff];
if (!unknownString && thisOp.Equals('+') && numStack.Count!=0 && numStack.Peek() is string) thisOp='c';
while (opStack.Count!=0 && numStack.Count!=0 && opOrder(thisOp)<=opOrder(opStack.Peek()))
evaluate_stack_tops(numStack, opStack, funcStack, false);
opStack.Push(thisOp);
lastwasop=true;
unknownString = false;
}
else if (charain[i] != ' ') {
buff = string.Empty;
if (charain[i].Equals('"')) {
while (i++!=charain.Length && charain[i]!='"')
buff += charain[i];
numStack.Push(buff);
}
else {
isnumber = true;
isfloat = infunc = inquotes = false;
pLevel = numParams = 0;
if (charain[i].Equals('-')) {
buff = "-";
i+=1;
}
while (i!=charain.Length && (!isOp(charain[i], 1) || infunc)) {
if (charain[i].Equals('(')) {
if (pLevel==0) {
funcStack.Push(buff);
buff = string.Empty;
opStack.Push('@');
infunc = true;
}
else buff += '(';
pLevel+=1;
}
else if (infunc && charain[i]==')') {
pLevel--;
if (pLevel==0) {
numStack.Push(Eval(buff));
numParams+=1;
}
else buff += ')';
}
else if (infunc && pLevel==1 && charain[i]==',') {
numStack.Push(Eval(buff));
numParams+=1;
buff = string.Empty;
}
else {
if (inquotes || charain[i]!=' ') {
if (charain[i] == '"') inquotes = !inquotes;
if (!(char.IsDigit(charain[i])||charain[i]=='.')) isnumber = false;
if (charain[i] == '.') isfloat=true;
buff += charain[i];
}
}
i+=1;
}
if (!infunc) {
if (isnumber) {
if (isfloat = true) numStack.Push(double.Parse(buff, Util.cfi)); // effectively disable ints (for now, at least)
else numStack.Push(int.Parse(buff, Util.cfi));
}
else if (constants.ContainsKey(buff.ToLowerInvariant())) numStack.Push(constants[buff.ToLowerInvariant()]);
else {
numStack.Push(buff);
//unknownString = true;
}
}
else numStack.Push(numParams);
i--;
}
lastwasop = false;
}
i+=1;
}
while (opStack.Count!=0 && numStack.Count!=0)
evaluate_stack_tops(numStack, opStack, funcStack, false);
if (numStack.Count != 0) return numStack.Pop();
else return false;
}
}
}