475 lines
8.8 KiB
C++
475 lines
8.8 KiB
C++
|
/*
|
||
|
* Copyright 2006 Haiku, Inc. All Rights Reserved.
|
||
|
* Distributed under the terms of the MIT License.
|
||
|
*
|
||
|
* Authors:
|
||
|
* Ingo Weinhold <bonefish@cs.tu-berlin.de>
|
||
|
* Stephan Aßmus <superstippi@gmx.de>
|
||
|
*/
|
||
|
|
||
|
#include <ctype.h>
|
||
|
#include <math.h>
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#include "ExpressionParser.h"
|
||
|
|
||
|
enum {
|
||
|
TOKEN_IDENTIFIER = 'a',
|
||
|
|
||
|
TOKEN_CONSTANT = '0',
|
||
|
|
||
|
TOKEN_PLUS = '+',
|
||
|
TOKEN_MINUS = '-',
|
||
|
|
||
|
TOKEN_STAR = '*',
|
||
|
TOKEN_SLASH = '/',
|
||
|
TOKEN_MODULO = '%',
|
||
|
|
||
|
TOKEN_POWER = '^',
|
||
|
|
||
|
TOKEN_OPENING_BRACKET = '(',
|
||
|
TOKEN_CLOSING_BRACKET = ')',
|
||
|
|
||
|
TOKEN_AND = '&',
|
||
|
TOKEN_OR = '|',
|
||
|
TOKEN_NOT = '~',
|
||
|
|
||
|
TOKEN_NONE = ' ',
|
||
|
TOKEN_END_OF_LINE = '\n',
|
||
|
};
|
||
|
|
||
|
|
||
|
struct Token {
|
||
|
Token()
|
||
|
: string(""),
|
||
|
type(TOKEN_NONE),
|
||
|
value(0.0),
|
||
|
position(0)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
Token(const Token& other)
|
||
|
: string(other.string),
|
||
|
type(other.type),
|
||
|
value(other.value),
|
||
|
position(other.position)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
Token(const char* string, int32 length, int32 position, int32 type)
|
||
|
: string(string, length),
|
||
|
type(type),
|
||
|
value(0.0),
|
||
|
position(position)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
Token& operator=(const Token& other)
|
||
|
{
|
||
|
string = other.string;
|
||
|
type = other.type;
|
||
|
value = other.value;
|
||
|
position = other.position;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
BString string;
|
||
|
int32 type;
|
||
|
double value;
|
||
|
|
||
|
int32 position;
|
||
|
};
|
||
|
|
||
|
|
||
|
class Tokenizer {
|
||
|
public:
|
||
|
Tokenizer()
|
||
|
: fString(""),
|
||
|
fCurrentChar(NULL),
|
||
|
fCurrentToken(),
|
||
|
fReuseToken(false)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void SetTo(const char* string)
|
||
|
{
|
||
|
fString = string;
|
||
|
fCurrentChar = fString.String();
|
||
|
fCurrentToken = Token();
|
||
|
fReuseToken = false;
|
||
|
}
|
||
|
|
||
|
const Token& NextToken()
|
||
|
{
|
||
|
if (fCurrentToken.type == TOKEN_END_OF_LINE)
|
||
|
return fCurrentToken;
|
||
|
|
||
|
if (fReuseToken) {
|
||
|
fReuseToken = false;
|
||
|
//printf("next token (recycled): '%s'\n", fCurrentToken.string.String());
|
||
|
return fCurrentToken;
|
||
|
}
|
||
|
|
||
|
while (*fCurrentChar != 0 && isspace(*fCurrentChar))
|
||
|
fCurrentChar++;
|
||
|
|
||
|
if (*fCurrentChar == 0)
|
||
|
return fCurrentToken = Token("", 0, _CurrentPos(), TOKEN_END_OF_LINE);
|
||
|
|
||
|
bool decimal = *fCurrentChar == '.' || *fCurrentChar == ',';
|
||
|
|
||
|
if (decimal || isdigit(*fCurrentChar)) {
|
||
|
|
||
|
BString temp;
|
||
|
|
||
|
const char* begin = fCurrentChar;
|
||
|
while (*fCurrentChar != 0) {
|
||
|
if (!isdigit(*fCurrentChar)) {
|
||
|
if (!(*fCurrentChar == '.' || *fCurrentChar == ','
|
||
|
|| *fCurrentChar == 'e' || *fCurrentChar == 'E'))
|
||
|
break;
|
||
|
}
|
||
|
if (*fCurrentChar == ',')
|
||
|
temp << '.';
|
||
|
else
|
||
|
temp << *fCurrentChar;
|
||
|
fCurrentChar++;
|
||
|
}
|
||
|
int32 length = fCurrentChar - begin;
|
||
|
temp << "&_";
|
||
|
double value;
|
||
|
char t[2];
|
||
|
int32 matches = sscanf(temp.String(), "%lf&%s", &value, t);
|
||
|
if (matches != 2)
|
||
|
throw ParseException("error in constant", _CurrentPos() - length);
|
||
|
|
||
|
fCurrentToken = Token(begin, length, _CurrentPos() - length, TOKEN_CONSTANT);
|
||
|
fCurrentToken.value = value;;
|
||
|
|
||
|
} else if (isalpha(*fCurrentChar)) {
|
||
|
|
||
|
const char* begin = fCurrentChar;
|
||
|
while (*fCurrentChar != 0 && (isalpha(*fCurrentChar) || isdigit(*fCurrentChar)))
|
||
|
fCurrentChar++;
|
||
|
int32 length = fCurrentChar - begin;
|
||
|
fCurrentToken = Token(begin, length, _CurrentPos() - length, TOKEN_IDENTIFIER);
|
||
|
|
||
|
} else {
|
||
|
|
||
|
switch (*fCurrentChar) {
|
||
|
case '+':
|
||
|
case '-':
|
||
|
case '*':
|
||
|
case '/':
|
||
|
|
||
|
case '^':
|
||
|
case '%':
|
||
|
|
||
|
case '(':
|
||
|
case ')':
|
||
|
|
||
|
case '&':
|
||
|
case '|':
|
||
|
case '~':
|
||
|
fCurrentToken = Token(fCurrentChar, 1, _CurrentPos(), *fCurrentChar);
|
||
|
fCurrentChar++;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
throw ParseException("unexpected character", _CurrentPos());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//printf("next token: '%s'\n", fCurrentToken.string.String());
|
||
|
return fCurrentToken;
|
||
|
}
|
||
|
|
||
|
void RewindToken()
|
||
|
{
|
||
|
fReuseToken = true;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
int32 _CurrentPos() const
|
||
|
{
|
||
|
return fCurrentChar - fString.String();
|
||
|
}
|
||
|
|
||
|
BString fString;
|
||
|
const char* fCurrentChar;
|
||
|
Token fCurrentToken;
|
||
|
bool fReuseToken;
|
||
|
};
|
||
|
|
||
|
|
||
|
ExpressionParser::ExpressionParser()
|
||
|
: fTokenizer(new Tokenizer())
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
ExpressionParser::~ExpressionParser()
|
||
|
{
|
||
|
delete fTokenizer;
|
||
|
}
|
||
|
|
||
|
|
||
|
double
|
||
|
ExpressionParser::Evaluate(const char* expressionString)
|
||
|
{
|
||
|
fTokenizer->SetTo(expressionString);
|
||
|
|
||
|
double value = _ParseBinary();
|
||
|
Token token = fTokenizer->NextToken();
|
||
|
if (token.type != TOKEN_END_OF_LINE)
|
||
|
throw ParseException("parse error", token.position);
|
||
|
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
|
||
|
double
|
||
|
ExpressionParser::_ParseBinary()
|
||
|
{
|
||
|
double value = _ParseSum();
|
||
|
|
||
|
while (true) {
|
||
|
Token token = fTokenizer->NextToken();
|
||
|
switch (token.type) {
|
||
|
case TOKEN_AND:
|
||
|
value = (uint64)value & (uint64)_ParseSum();
|
||
|
break;
|
||
|
case TOKEN_OR:
|
||
|
value = (uint64)value | (uint64)_ParseSum();
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
fTokenizer->RewindToken();
|
||
|
return value;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
double
|
||
|
ExpressionParser::_ParseSum()
|
||
|
{
|
||
|
// TODO: check isnan()...
|
||
|
double value = _ParseProduct();
|
||
|
|
||
|
while (true) {
|
||
|
Token token = fTokenizer->NextToken();
|
||
|
switch (token.type) {
|
||
|
case TOKEN_PLUS:
|
||
|
value = value + _ParseProduct();
|
||
|
break;
|
||
|
case TOKEN_MINUS:
|
||
|
value = value - _ParseProduct();
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
fTokenizer->RewindToken();
|
||
|
return value;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
double
|
||
|
ExpressionParser::_ParseProduct()
|
||
|
{
|
||
|
// TODO: check isnan()...
|
||
|
double value = _ParsePower();
|
||
|
|
||
|
while (true) {
|
||
|
Token token = fTokenizer->NextToken();
|
||
|
switch (token.type) {
|
||
|
case TOKEN_STAR:
|
||
|
value = value * _ParsePower();
|
||
|
break;
|
||
|
case TOKEN_SLASH: {
|
||
|
double rhs = _ParsePower();
|
||
|
if (rhs == 0.0)
|
||
|
throw ParseException("division by zero", token.position);
|
||
|
value = value / rhs;
|
||
|
break;
|
||
|
}
|
||
|
case TOKEN_MODULO: {
|
||
|
double rhs = _ParsePower();
|
||
|
if (rhs == 0.0)
|
||
|
throw ParseException("modulo by zero", token.position);
|
||
|
value = fmod(value, rhs);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
fTokenizer->RewindToken();
|
||
|
return value;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
double
|
||
|
ExpressionParser::_ParsePower()
|
||
|
{
|
||
|
double value = _ParseUnary();
|
||
|
|
||
|
while (true) {
|
||
|
Token token = fTokenizer->NextToken();
|
||
|
if (token.type != TOKEN_POWER) {
|
||
|
fTokenizer->RewindToken();
|
||
|
return value;
|
||
|
}
|
||
|
value = pow(value, _ParseUnary());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
double
|
||
|
ExpressionParser::_ParseUnary()
|
||
|
{
|
||
|
Token token = fTokenizer->NextToken();
|
||
|
if (token.type == TOKEN_END_OF_LINE)
|
||
|
throw ParseException("unexpected end of expression", token.position);
|
||
|
|
||
|
switch (token.type) {
|
||
|
case TOKEN_PLUS:
|
||
|
return _ParseUnary();
|
||
|
case TOKEN_MINUS:
|
||
|
return -_ParseUnary();
|
||
|
case TOKEN_NOT:
|
||
|
return ~(uint64)_ParseUnary();
|
||
|
|
||
|
case TOKEN_IDENTIFIER:
|
||
|
return _ParseFunction(token);
|
||
|
|
||
|
default:
|
||
|
fTokenizer->RewindToken();
|
||
|
return _ParseAtom();
|
||
|
}
|
||
|
|
||
|
return 0.0;
|
||
|
}
|
||
|
|
||
|
|
||
|
struct Function {
|
||
|
const char* name;
|
||
|
int argumentCount;
|
||
|
void* function;
|
||
|
double value;
|
||
|
};
|
||
|
|
||
|
static const Function kFunctions[] = {
|
||
|
{ "e", 0, NULL, M_E },
|
||
|
{ "pi", 0, NULL, M_PI },
|
||
|
|
||
|
{ "abs", 1, &fabs },
|
||
|
{ "acos", 1, &acos },
|
||
|
{ "asin", 1, &asin },
|
||
|
{ "atan", 1, &atan },
|
||
|
{ "atan2", 2, &atan2 },
|
||
|
{ "ceil", 1, &ceil },
|
||
|
{ "cos", 1, &cos },
|
||
|
{ "cosh", 1, &cosh },
|
||
|
{ "exp", 1, &exp },
|
||
|
{ "fabs", 1, &fabs },
|
||
|
{ "floor", 1, &floor },
|
||
|
{ "fmod", 2, &fmod },
|
||
|
{ "log", 1, &log },
|
||
|
{ "log10", 1, &log10 },
|
||
|
{ "pow", 2, &pow },
|
||
|
{ "sin", 1, &sin },
|
||
|
{ "sinh", 1, &sinh },
|
||
|
{ "sqrt", 1, &sqrt },
|
||
|
{ "tan", 1, &tan },
|
||
|
{ "tanh", 1, &tanh },
|
||
|
{ "hypot", 2, &hypot },
|
||
|
|
||
|
{ NULL },
|
||
|
};
|
||
|
|
||
|
double
|
||
|
ExpressionParser::_ParseFunction(const Token& token)
|
||
|
{
|
||
|
const Function* function = _FindFunction(token.string.String());
|
||
|
if (!function)
|
||
|
throw ParseException("unknown identifier", token.position);
|
||
|
|
||
|
if (function->argumentCount == 0)
|
||
|
return function->value;
|
||
|
|
||
|
_EatToken(TOKEN_OPENING_BRACKET);
|
||
|
|
||
|
double values[function->argumentCount];
|
||
|
for (int32 i = 0; i < function->argumentCount; i++) {
|
||
|
values[i] = _ParseBinary();
|
||
|
}
|
||
|
|
||
|
_EatToken(TOKEN_CLOSING_BRACKET);
|
||
|
|
||
|
// hard coded cases for different count of arguments
|
||
|
switch (function->argumentCount) {
|
||
|
case 1:
|
||
|
return ((double (*)(double))function->function)(values[0]);
|
||
|
case 2:
|
||
|
return ((double (*)(double, double))function->function)(values[0],
|
||
|
values[1]);
|
||
|
case 3:
|
||
|
return ((double (*)(double, double, double))function->function)(
|
||
|
values[0], values[1], values[2]);
|
||
|
|
||
|
default:
|
||
|
throw ParseException("unsupported function argument count",
|
||
|
token.position);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
double
|
||
|
ExpressionParser::_ParseAtom()
|
||
|
{
|
||
|
Token token = fTokenizer->NextToken();
|
||
|
if (token.type == TOKEN_END_OF_LINE)
|
||
|
throw ParseException("unexpected end of expression", token.position);
|
||
|
|
||
|
if (token.type == TOKEN_CONSTANT)
|
||
|
return token.value;
|
||
|
|
||
|
fTokenizer->RewindToken();
|
||
|
|
||
|
_EatToken(TOKEN_OPENING_BRACKET);
|
||
|
|
||
|
double value = _ParseBinary();
|
||
|
|
||
|
_EatToken(TOKEN_CLOSING_BRACKET);
|
||
|
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
ExpressionParser::_EatToken(int32 type)
|
||
|
{
|
||
|
Token token = fTokenizer->NextToken();
|
||
|
if (token.type != type) {
|
||
|
BString temp("expected '");
|
||
|
temp << (char)type << "' got '" << token.string << "'";
|
||
|
throw ParseException(temp.String(), token.position);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
const Function*
|
||
|
ExpressionParser::_FindFunction(const char* name) const
|
||
|
{
|
||
|
for (int32 i = 0; kFunctions[i].name; i++) {
|
||
|
if (strcasecmp(kFunctions[i].name, name) == 0)
|
||
|
return &kFunctions[i];
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|