Aegisub/OverLua/expression_engine.cpp

574 lines
12 KiB
C++

/*
* OverLua expression engine
*
Copyright 2007 Niels Martin Hansen
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; either version 2 of the License, or
(at your option) any later version.
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.
Contact:
E-mail: <jiifurusu@gmail.com>
IRC: jfs in #aegisub on irc.rizon.net
*/
#include "expression_engine.h"
#include <math.h>
namespace ExpressionEngine {
// Builtin functions
static bool f_abs(Stack &stack, void *data)
{
if (stack.size() >= 1) {
double v = stack.back();
stack.pop_back();
stack.push_back(fabs(v));
return true;
} else {
return false;
}
}
static const Function fs_abs = {"abs", f_abs};
static bool f_floor(Stack &stack, void *data)
{
if (stack.size() >= 1) {
double v = stack.back();
stack.pop_back();
stack.push_back(floor(v));
return true;
} else {
return false;
}
}
static const Function fs_floor = {"floor", f_floor};
static bool f_ceil(Stack &stack, void *data)
{
if (stack.size() >= 1) {
double v = stack.back();
stack.pop_back();
stack.push_back(ceil(v));
return true;
} else {
return false;
}
}
static const Function fs_ceil = {"ceil", f_ceil};
static bool f_log(Stack &stack, void *data)
{
if (stack.size() >= 1) {
double v = stack.back();
stack.pop_back();
stack.push_back(log(v));
return true;
} else {
return false;
}
}
static const Function fs_log = {"log", f_log};
static bool f_exp(Stack &stack, void *data)
{
if (stack.size() >= 1) {
double v = stack.back();
stack.pop_back();
stack.push_back(exp(v));
return true;
} else {
return false;
}
}
static const Function fs_exp = {"exp", f_exp};
static bool f_sqrt(Stack &stack, void *data)
{
if (stack.size() >= 1) {
double v = stack.back();
stack.pop_back();
stack.push_back(sqrt(v));
return true;
} else {
return false;
}
}
static const Function fs_sqrt = {"sqrt", f_sqrt};
static bool f_e(Stack &stack, void *data)
{
stack.push_back(2.71828182845904523536);
return true;
}
static const Function fs_e = {"e", f_e};
static bool f_min(Stack &stack, void *data)
{
if (stack.size() >= 2) {
double v1 = stack.back();
stack.pop_back();
double v2 = stack.back();
stack.pop_back();
if (v1 < v2)
stack.push_back(v1);
else
stack.push_back(v2);
return true;
} else {
return false;
}
}
static const Function fs_min = {"min", f_min};
static bool f_max(Stack &stack, void *data)
{
if (stack.size() >= 2) {
double v1 = stack.back();
stack.pop_back();
double v2 = stack.back();
stack.pop_back();
if (v1 > v2)
stack.push_back(v1);
else
stack.push_back(v2);
return true;
} else {
return false;
}
}
static const Function fs_max = {"max", f_max};
static bool f_pi(Stack &stack, void *data)
{
stack.push_back(3.14159265358979323846);
return true;
}
static const Function fs_pi = {"pi", f_pi};
static bool f_sin(Stack &stack, void *data)
{
if (stack.size() >= 1) {
double v = stack.back();
stack.pop_back();
stack.push_back(sin(v));
return true;
} else {
return false;
}
}
static const Function fs_sin = {"sin", f_sin};
static bool f_cos(Stack &stack, void *data)
{
if (stack.size() >= 1) {
double v = stack.back();
stack.pop_back();
stack.push_back(cos(v));
return true;
} else {
return false;
}
}
static const Function fs_cos = {"cos", f_cos};
static bool f_tan(Stack &stack, void *data)
{
if (stack.size() >= 1) {
double v = stack.back();
stack.pop_back();
stack.push_back(tan(v));
return true;
} else {
return false;
}
}
static const Function fs_tan = {"tan", f_tan};
static bool f_asin(Stack &stack, void *data)
{
if (stack.size() >= 1) {
double v = stack.back();
stack.pop_back();
stack.push_back(asin(v));
return true;
} else {
return false;
}
}
static const Function fs_asin = {"asin", f_asin};
static bool f_acos(Stack &stack, void *data)
{
if (stack.size() >= 1) {
double v = stack.back();
stack.pop_back();
stack.push_back(acos(v));
return true;
} else {
return false;
}
}
static const Function fs_acos = {"acos", f_acos};
static bool f_atan(Stack &stack, void *data)
{
if (stack.size() >= 1) {
double v = stack.back();
stack.pop_back();
stack.push_back(atan(v));
return true;
} else {
return false;
}
}
static const Function fs_atan = {"atan", f_atan};
static bool f_mod(Stack &stack, void *data)
{
if (stack.size() >= 2) {
double v1 = stack.back();
stack.pop_back();
double v2 = stack.back();
stack.pop_back();
stack.push_back(fmod(v2, v1));
return true;
} else {
return false;
}
}
static const Function fs_mod = {"mod", f_mod};
static bool f_rand(Stack &stack, void *data)
{
stack.push_back((double)rand()/RAND_MAX);
return true;
}
static const Function fs_rand = {"rand", f_rand};
static bool f_ifgtz(Stack &stack, void *data)
{
if (stack.size() >= 2) {
double v1 = stack.back();
stack.pop_back();
double v2 = stack.back();
stack.pop_back();
double v3 = stack.back();
stack.pop_back();
if (v3 > 0)
stack.push_back(v2);
else
stack.push_back(v1);
return true;
} else {
return false;
}
}
static const Function fs_ifgtz = {"ifgtz", f_ifgtz};
static bool f_ifeqz(Stack &stack, void *data)
{
if (stack.size() >= 2) {
double v1 = stack.back();
stack.pop_back();
double v2 = stack.back();
stack.pop_back();
double v3 = stack.back();
stack.pop_back();
if (v3 == 0)
stack.push_back(v2);
else
stack.push_back(v1);
return true;
} else {
return false;
}
}
static const Function fs_ifeqz = {"ifeqz", f_ifeqz};
// Machine specification
Specification::Specification()
{
// Add standard functions
functions.push_back(fs_abs);
functions.push_back(fs_floor);
functions.push_back(fs_ceil);
functions.push_back(fs_log);
functions.push_back(fs_exp);
functions.push_back(fs_sqrt);
functions.push_back(fs_e);
functions.push_back(fs_min);
functions.push_back(fs_max);
functions.push_back(fs_pi);
functions.push_back(fs_sin);
functions.push_back(fs_cos);
functions.push_back(fs_tan);
functions.push_back(fs_asin);
functions.push_back(fs_acos);
functions.push_back(fs_atan);
functions.push_back(fs_mod);
functions.push_back(fs_rand);
functions.push_back(fs_ifgtz);
functions.push_back(fs_ifeqz);
}
// Machine runner
bool Machine::Run()
{
// Prepare the stack
std::vector<double> stack;
stack.reserve(16);
// Assume the registers are already initialised like the manager wants
// Execute the program
for (size_t pc = 0; pc < program.size(); pc++) {
Instruction &i = program[pc];
switch (i.op) {
double v1, v2; // values for operators;
case INST_PUSH_CONST:
stack.push_back(i.vd);
break;
case INST_PUSH_REG:
stack.push_back(registers[i.vu]);
break;
case INST_ADD:
if (stack.size() < 2) return false;
v1 = stack.back(); stack.pop_back();
v2 = stack.back(); stack.pop_back();
stack.push_back(v2+v1);
break;
case INST_SUB:
if (stack.size() < 2) return false;
v1 = stack.back(); stack.pop_back();
v2 = stack.back(); stack.pop_back();
stack.push_back(v2-v1);
break;
case INST_MUL:
if (stack.size() < 2) return false;
v1 = stack.back(); stack.pop_back();
v2 = stack.back(); stack.pop_back();
stack.push_back(v2*v1);
break;
case INST_DIV:
if (stack.size() < 2) return false;
v1 = stack.back(); stack.pop_back();
v2 = stack.back(); stack.pop_back();
stack.push_back(v2/v1);
break;
case INST_POW:
if (stack.size() < 2) return false;
v1 = stack.back(); stack.pop_back();
v2 = stack.back(); stack.pop_back();
stack.push_back(pow(v2, v1));
break;
case INST_UNM:
if (stack.size() < 1) return false;
v1 = stack.back(); stack.pop_back();
stack.push_back(-v1);
break;
case INST_CALL:
if (!i.vf(stack, i.vfd))
return false;
break;
case INST_STORE:
if (stack.size() < 1) return false;
v1 = stack.back(); stack.pop_back();
registers[i.vu] = v1;
break;
default:
return false;
}
}
// The registers should now be in the final state
return true;
}
static const char *parse_register_name(const char *source, const std::vector<std::string> &registers, size_t &index)
{
// Find end of the potential register name
// That is, end of string or whitespace
const char *end = source;
while (*end && *end != ' ' && *end != '\t' && *end != '\n' && *end != '\r') end++;
// Now end points to one past last character in name
std::string regname(source, end-source);
if (regname.size() == 0) return 0;
// Check for supplied register name
for (size_t i = 0; i < registers.size(); i++) {
if (regname == registers[i]) {
index = i;
return end;
}
}
// Check for temp register name
if (regname[0] == 't' && regname.size() == 2) {
if (regname[1] >= '0' && regname[1] <= '9') {
index = registers.size() + regname[1] - '0';
return end;
}
}
// Nothing matches
return 0;
}
static const char *parse_function_name(const char *source, const std::vector<Function> &functions, FunctionPtr &funcptr, void *&funcdata)
{
// Find end of the potential function name
// That is, end of string or whitespace
const char *end = source;
while (*end && *end != ' ' && *end != '\t' && *end != '\n' && *end != '\r') end++;
// Now end points to one past last character in name
std::string funcname(source, end-source);
if (funcname.size() == 0) return 0;
// Check for supplied register name
for (size_t i = 0; i < functions.size(); i++) {
if (funcname == functions[i].name) {
funcptr = functions[i].function;
funcdata = functions[i].data;
return end;
}
}
return 0;
}
Machine::Machine(const ExpressionEngine::Specification &spec, const char *source)
{
// Set up the registers
const size_t temp_register_count = 10;
registers.resize(spec.registers.size() + temp_register_count);
program.reserve(64);
// Parse the program
while (*source) {
Instruction i;
// Skip whitespace
while (*source && (*source == ' ' || *source == '\t' || *source == '\n' || *source == '\r')) source++;
if (!*source) break;
// First see if it can be read as a number constant
{
char *tmp = 0;
double v = strtod(source, &tmp);
if (tmp && source != tmp) {
// Could be read, so we have a constant here
source = tmp;
i.op = INST_PUSH_CONST;
i.vd = v;
program.push_back(i);
continue;
}
}
// Not a number constant
// Check for arithmetic operator
if (*source == '+') {
source++;
i.op = INST_ADD;
program.push_back(i);
}
else if (*source == '-') {
source++;
i.op = INST_SUB;
program.push_back(i);
}
else if (*source == '*') {
source++;
i.op = INST_MUL;
program.push_back(i);
}
else if (*source == '/') {
source++;
i.op = INST_DIV;
program.push_back(i);
}
else if (*source == '^') {
source++;
i.op = INST_POW;
program.push_back(i);
}
else if (*source == '~') {
source++;
i.op = INST_UNM;
program.push_back(i);
}
// Check for assignment
else if (*source == '=') {
i.op = INST_STORE;
const char *tmp = parse_register_name(source+1, spec.registers, i.vu);
if (!tmp) throw source; // No register name found, error
source = tmp;
program.push_back(i);
}
// Register push or function call
else {
const char *tmp = parse_register_name(source, spec.registers, i.vu);
if (tmp) {
// Register push
i.op = INST_PUSH_REG;
source = tmp;
program.push_back(i);
}
else {
tmp = parse_function_name(source, spec.functions, i.vf, i.vfd);
if (tmp) {
// Function call
i.op = INST_CALL;
source = tmp;
program.push_back(i);
}
else {
// Nothing, error
throw source;
}
}
}
} /* end while */
} /* end Machine::Machine() */
};