From 3b5e953c707f211c0989c6bfe802c2e6c5b6a1f1 Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Fri, 17 Aug 2007 23:26:36 +0000 Subject: [PATCH] Expression evaluator implemented and compiles... probably doesn't work Originally committed to SVN as r1506. --- OverLua/docs/expression-evaluator.txt | 4 - OverLua/expression_engine.cpp | 573 ++++++++++++++++++++++++++ OverLua/expression_engine.h | 113 +++++ 3 files changed, 686 insertions(+), 4 deletions(-) create mode 100644 OverLua/expression_engine.cpp create mode 100644 OverLua/expression_engine.h diff --git a/OverLua/docs/expression-evaluator.txt b/OverLua/docs/expression-evaluator.txt index f7c50dacf..2cb3ad7eb 100644 --- a/OverLua/docs/expression-evaluator.txt +++ b/OverLua/docs/expression-evaluator.txt @@ -137,10 +137,6 @@ ceil Takes one argument, produces one result. Round towards positive infinity. -trunc -Takes one argument, produces one result. -Round towards zero. - log Takes one argument, produces one result. diff --git a/OverLua/expression_engine.cpp b/OverLua/expression_engine.cpp new file mode 100644 index 000000000..89830d185 --- /dev/null +++ b/OverLua/expression_engine.cpp @@ -0,0 +1,573 @@ +/* + * 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: + IRC: jfs in #aegisub on irc.rizon.net + + */ + + +#include "expression_engine.h" +#include + + +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 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 ®isters, 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 &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() */ + +}; diff --git a/OverLua/expression_engine.h b/OverLua/expression_engine.h new file mode 100644 index 000000000..34a4da00d --- /dev/null +++ b/OverLua/expression_engine.h @@ -0,0 +1,113 @@ +/* + * 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: + IRC: jfs in #aegisub on irc.rizon.net + + */ + +#ifndef EXPRESSION_ENGINE_H +#define EXPRESSION_ENGINE_H + +#include +#include + + +namespace ExpressionEngine { + + // The stack passed around + typedef std::vector Stack; + + // Type of callable functions + typedef bool (*FunctionPtr)(Stack &stack, void *data); + + // A callable function + struct Function { + // The name of the function + const char *name; + // Function pointer; null for builtins + FunctionPtr function; + // Function data + void *data; + }; + + // A machine specification that can compile programs + struct Specification { + // The input and output register names + // The index of a register name in this vector translates to the register's number in the machine + std::vector registers; + + // The callable functions + std::vector functions; + + // Just adds standard functions to functions vector + Specification(); + }; + + // Operation type + enum Opcode { + INST_PUSH_CONST = 0, + INST_PUSH_REG, + INST_ADD, + INST_SUB, + INST_MUL, + INST_DIV, + INST_POW, + INST_UNM, // unary minus + INST_CALL, + INST_STORE + }; + + // A single program instruction + struct Instruction { + Opcode op; + union { + double vd; // double value, for const push + size_t vu; // uint value, for register indices + struct { + FunctionPtr vf; // function to call + void *vfd; // data for function + }; + }; + }; + + // A program is a sequence of instructions + typedef std::vector Program; + + // A machine running a program + struct Machine { + // Values in the registers + std::vector registers; + // The program + Program program; + + // Run the machine. + // Return true is no errors, false if error + bool Run(); + + // Create a machine from a specification and a program source + Machine(const Specification &spec, const char *source); + }; + +}; + + +#endif