redquark-amiberry-rb/src/calc.cpp

419 lines
9.7 KiB
C++

/*
* UAE - The Un*x Amiga Emulator
*
* Infix->RPN conversion and calculation
*
*/
/*
Original code from http://en.wikipedia.org/wiki/Shunting_yard_algorithm
*/
#include "sysdeps.h"
#define STACK_SIZE 32
#define MAX_VALUES 32
#define IOBUFFERS 256
static double parsedvalues[MAX_VALUES];
// operators
// precedence operators associativity
// 1 ! right to left
// 2 * / % left to right
// 3 + - left to right
// 4 = right to left
static int op_preced(const TCHAR c)
{
switch(c) {
case '!':
return 4;
case '*': case '/': case '\\': case '%':
return 3;
case '+': case '-':
return 2;
case '=':
return 1;
}
return 0;
}
static bool op_left_assoc(const TCHAR c)
{
switch(c) {
// left to right
case '*': case '/': case '%': case '+': case '-':
return true;
// right to left
case '=': case '!':
return false;
}
return false;
}
static unsigned int op_arg_count(const TCHAR c)
{
switch(c) {
case '*': case '/': case '%': case '+': case '-': case '=':
return 2;
case '!':
return 1;
default:
return c - 'A';
}
return 0;
}
#define is_operator(c) (c == '+' || c == '-' || c == '/' || c == '*' || c == '!' || c == '%' || c == '=')
#define is_function(c) (c >= 'A' && c <= 'Z')
#define is_ident(c) ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z'))
static bool shunting_yard(const TCHAR *input, TCHAR *output)
{
const TCHAR *strpos = input, *strend = input + _tcslen(input);
TCHAR c, *outpos = output;
TCHAR stack[STACK_SIZE]; // operator stack
unsigned int sl = 0; // stack length
TCHAR sc; // used for record stack element
while(strpos < strend) {
if (sl >= STACK_SIZE)
return false;
// read one token from the input stream
c = *strpos;
if(c != ' ') {
// If the token is a number (identifier), then add it to the output queue.
if(is_ident(c)) {
*outpos = c; ++outpos;
}
// If the token is a function token, then push it onto the stack.
else if(is_function(c)) {
stack[sl] = c;
++sl;
}
// If the token is a function argument separator (e.g., a comma):
else if(c == ',') {
bool pe = false;
while(sl > 0) {
sc = stack[sl - 1];
if(sc == '(') {
pe = true;
break;
}
else {
// Until the token at the top of the stack is a left parenthesis,
// pop operators off the stack onto the output queue.
*outpos = sc;
++outpos;
sl--;
}
}
// If no left parentheses are encountered, either the separator was misplaced
// or parentheses were mismatched.
if(!pe) {
return false;
}
}
// If the token is an operator, op1, then:
else if(is_operator(c)) {
while(sl > 0) {
sc = stack[sl - 1];
// While there is an operator token, o2, at the top of the stack
// op1 is left-associative and its precedence is less than or equal to that of op2,
// or op1 is right-associative and its precedence is less than that of op2,
if(is_operator(sc) &&
((op_left_assoc(c) && (op_preced(c) <= op_preced(sc))) ||
(!op_left_assoc(c) && (op_preced(c) < op_preced(sc))))) {
// Pop o2 off the stack, onto the output queue;
*outpos = sc;
++outpos;
sl--;
}
else {
break;
}
}
// push op1 onto the stack.
stack[sl] = c;
++sl;
}
// If the token is a left parenthesis, then push it onto the stack.
else if(c == '(') {
stack[sl] = c;
++sl;
}
// If the token is a right parenthesis:
else if(c == ')') {
bool pe = false;
// Until the token at the top of the stack is a left parenthesis,
// pop operators off the stack onto the output queue
while(sl > 0) {
sc = stack[sl - 1];
if(sc == '(') {
pe = true;
break;
}
else {
*outpos = sc;
++outpos;
sl--;
}
}
// If the stack runs out without finding a left parenthesis, then there are mismatched parentheses.
if(!pe) {
return false;
}
// Pop the left parenthesis from the stack, but not onto the output queue.
sl--;
// If the token at the top of the stack is a function token, pop it onto the output queue.
if(sl > 0) {
sc = stack[sl - 1];
if(is_function(sc)) {
*outpos = sc;
++outpos;
sl--;
}
}
}
else {
return false; // Unknown token
}
}
++strpos;
}
// When there are no more tokens to read:
// While there are still operator tokens in the stack:
while(sl > 0) {
sc = stack[sl - 1];
if(sc == '(' || sc == ')') {
printf("Error: parentheses mismatched\n");
return false;
}
*outpos = sc;
++outpos;
--sl;
}
*outpos = 0; // Null terminator
return true;
}
struct calcstack
{
TCHAR *s;
double val;
};
static double docalcx(TCHAR op, double v1, double v2)
{
switch (op)
{
case '-':
return v1 - v2;
case '+':
return v1 + v2;
case '*':
return v1 * v2;
case '/':
return v1 / v2;
case '\\':
return (int)v1 % (int)v2;
}
return 0;
}
static double stacktoval(struct calcstack *st)
{
if (st->s) {
if (_tcslen(st->s) == 1 && st->s[0] >= 'a' && st->s[0] <= 'z')
return parsedvalues[st->s[0] - 'a'];
return _tstof (st->s);
} else {
return st->val;
}
}
static double docalc2(TCHAR op, struct calcstack *sv1, struct calcstack *sv2)
{
double v1, v2;
v1 = stacktoval(sv1);
v2 = stacktoval(sv2);
return docalcx (op, v1, v2);
}
static double docalc1(TCHAR op, struct calcstack *sv1, double v2)
{
double v1;
v1 = stacktoval(sv1);
return docalcx (op, v1, v2);
}
static TCHAR *chartostack(TCHAR c)
{
TCHAR *s = xmalloc (TCHAR, 2);
s[0] = c;
s[1] = 0;
return s;
}
static bool execution_order(const TCHAR *input, double *outval)
{
const TCHAR *strpos = input, *strend = input + _tcslen(input);
TCHAR c, res[4];
unsigned int sl = 0, rn = 0;
struct calcstack stack[STACK_SIZE] = { { 0 } }, *sc, *sc2;
double val = 0;
int i;
bool ok = false;
// While there are input tokens left
while(strpos < strend) {
if (sl >= STACK_SIZE)
return false;
// Read the next token from input.
c = *strpos;
// If the token is a value or identifier
if(is_ident(c)) {
// Push it onto the stack.
stack[sl].s = chartostack (c);
++sl;
}
// Otherwise, the token is an operator (operator here includes both operators, and functions).
else if(is_operator(c) || is_function(c)) {
_stprintf(res, _T("_%02d"), rn);
++rn;
// It is known a priori that the operator takes n arguments.
unsigned int nargs = op_arg_count(c);
// If there are fewer than n values on the stack
if(sl < nargs) {
// (Error) The user has not input sufficient values in the expression.
return false;
}
// Else, Pop the top n values from the stack.
// Evaluate the operator, with the values as arguments.
if(is_function(c)) {
while(nargs > 0){
sc = &stack[sl - nargs]; // to remove reverse order of arguments
--nargs;
}
sl-=op_arg_count(c);
}
else {
if(nargs == 1) {
sc = &stack[sl - 1];
sl--;
val = docalc1 (c, sc, val);
}
else {
sc = &stack[sl - 2];
sc2 = &stack[sl - 1];
val = docalc2 (c, sc, sc2);
sl--;sl--;
}
}
// Push the returned results, if any, back onto the stack.
stack[sl].val = val;
stack[sl].s = NULL;
++sl;
}
++strpos;
}
// If there is only one value in the stack
// That value is the result of the calculation.
if(sl == 1) {
sc = &stack[sl - 1];
sl--;
if (outval)
*outval = val;
ok = true;
}
for (i = 0; i < STACK_SIZE; i++)
xfree (stack[i].s);
// If there are more values in the stack
// (Error) The user input has too many values.
return ok;
}
static bool is_separator(TCHAR c)
{
if (is_operator(c))
return true;
return c == 0 || c == ')' || c == ' ';
}
static bool parse_values(const TCHAR *ins, TCHAR *out)
{
int ident = 0;
TCHAR tmp;
TCHAR inbuf[IOBUFFERS];
int op;
_tcscpy (inbuf, ins);
TCHAR *in = inbuf;
TCHAR *p = out;
op = 0;
if (in[0] == '-' || in[0] == '+') {
*p++ = '0';
}
while (*in) {
TCHAR *instart = in;
if (!_tcsncmp(in, _T("true"), 4) && is_separator(in[4])) {
in[0] = '1';
in[1] = ' ';
in[2] = ' ';
in[3] = ' ';
} else if (!_tcsncmp(in, _T("false"), 5) && is_separator(in[5])) {
in[0] = '0';
in[1] = ' ';
in[2] = ' ';
in[3] = ' ';
in[4] = ' ';
}
if (_istdigit (*in)) {
if (ident >= MAX_VALUES)
return false;
if (op > 1 && (in[-1] == '-' || in[-1] == '+')) {
instart--;
p--;
}
*p++ = ident + 'a';
while (_istdigit (*in) || *in == '.')
in++;
tmp = *in;
*in = 0;
parsedvalues[ident++] = _tstof (instart);
*in = tmp;
op = 0;
} else {
if (is_operator(*in))
op++;
*p++ = *in++;
}
}
*p = 0;
return true;
}
bool calc(const TCHAR *input, double *outval)
{
TCHAR output[IOBUFFERS], output2[IOBUFFERS];
if (parse_values(input, output2)) {
if(shunting_yard(output2, output)) {
if(!execution_order(output, outval)) {
} else {
return true;
}
}
}
return false;
}