summaryrefslogtreecommitdiff
path: root/dviware/dvisvgm/src/Calculator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dviware/dvisvgm/src/Calculator.cpp')
-rw-r--r--dviware/dvisvgm/src/Calculator.cpp228
1 files changed, 122 insertions, 106 deletions
diff --git a/dviware/dvisvgm/src/Calculator.cpp b/dviware/dvisvgm/src/Calculator.cpp
index 0fdcf0f2fa..00490efa04 100644
--- a/dviware/dvisvgm/src/Calculator.cpp
+++ b/dviware/dvisvgm/src/Calculator.cpp
@@ -2,7 +2,7 @@
** Calculator.cpp **
** **
** This file is part of dvisvgm -- a fast DVI to SVG converter **
-** Copyright (C) 2005-2020 Martin Gieseking <martin.gieseking@uos.de> **
+** Copyright (C) 2005-2021 Martin Gieseking <martin.gieseking@uos.de> **
** **
** This program is free software; you can redistribute it and/or **
** modify it under the terms of the GNU General Public License as **
@@ -25,20 +25,19 @@
using namespace std;
-// token types
-const char END = 0;
-const char NUMBER = 1;
-const char NAME = 2;
-
-
/** Evaluates a given arithmetic expression and returns its value.
* The evaluator is implemented as a recursive descent parser.
* @param[in] is reads expression from this stream
* @return expression value */
-double Calculator::eval (istream &is) {
+double Calculator::eval (istream &is) const {
double ret = expr(is, false);
- if (lookAhead(is) > 0)
+ try {
+ // check if expression has been fully evaluated (next token is of type bool)
+ mpark::get<bool>(lookAhead(is));
+ }
+ catch (mpark::bad_variant_access &e) {
throw CalculatorException("expression syntax error");
+ }
return ret;
}
@@ -46,7 +45,7 @@ double Calculator::eval (istream &is) {
/** Evaluates a given arithmetic expression and returns its value.
* @param[in] expr expression to evaluate
* @return expression value */
-double Calculator::eval (const string &expr) {
+double Calculator::eval (const string &expr) const {
istringstream iss;
iss.str(expr);
return eval(iss);
@@ -54,133 +53,150 @@ double Calculator::eval (const string &expr) {
/** Evaluates the root rule of the expression grammar. */
-double Calculator::expr (istream &is, bool skip) { // expr:
+double Calculator::expr (istream &is, bool skip) const { // expr:
double left = term(is, skip);
- for (;;) {
- switch (lookAhead(is)) {
- case '+': left += term(is, true); break; // term '+' term => $1 + $3
- case '-': left -= term(is, true); break; // term '-' term => $1 - $3
- default : return left; // term => $1
+ bool ready=false;
+ while (!ready) {
+ Token token = lookAhead(is);
+ char *op = mpark::get_if<char>(&token);
+ if (!op)
+ ready = true;
+ else {
+ switch (*op) {
+ case '+': left += term(is, true); break; // term '+' term => $1 + $3
+ case '-': left -= term(is, true); break; // term '-' term => $1 - $3
+ default : ready = true;
+ }
}
}
+ return left; // term => $1
}
-double Calculator::term (istream &is, bool skip) { // term:
+double Calculator::term (istream &is, bool skip) const { // term:
double left = prim(is, skip);
- for (;;)
- switch (lookAhead(is)) {
- case '*': left *= prim(is, true); break; // prim '*' prim => $1 * $3
- case '(': left *= prim(is, false); break; // prim '*' prim => $1 * $3
- case '/': { // prim '/' prim => $1 / $3
- double denom = prim(is, true);
- if (denom == 0)
- throw CalculatorException("division by zero");
- left /= denom;
- break;
+ bool ready=false;
+ while (!ready) {
+ Token token = lookAhead(is);
+ char *op = mpark::get_if<char>(&token);
+ if (!op)
+ ready = true;
+ else {
+ switch (*op) {
+ case '*': left *= prim(is, true); break; // prim '*' prim => $1 * $3
+ case '(': left *= prim(is, false); break; // prim '(' prim => $1 * $3
+ case '/': { // prim '/' prim => $1 / $3
+ double denom = prim(is, true);
+ if (denom == 0)
+ throw CalculatorException("division by zero");
+ left /= denom;
+ break;
+ }
+ case '%': { // prim '%' prim => $1 mod $3
+ double denom = prim(is, true);
+ if (denom == 0)
+ throw CalculatorException("division by zero");
+ left -= denom * floor(left / denom);
+ break;
+ }
+ default:
+ ready = true;
}
- case '%': { // prim '%' prim => $1 mod $3
- double denom = prim(is, true);
- if (denom == 0)
- throw CalculatorException("division by zero");
- left -= denom*floor(left/denom);
- break;
- }
- default: // prim => $1
- return left;
}
+ }
+ return left;
}
-double Calculator::prim (istream &is, bool skip) { // prim:
+/** Evaluates a primary expression of the grammar which doesn't contain any binary operators. */
+double Calculator::prim (istream &is, bool skip) const { // prim:
if (skip)
lex(is);
- switch (lookAhead(is)) {
- case NUMBER: { // NUMBER => $1
- lex(is);
- double ret = _numValue;
- if (lookAhead(is) == NAME) { // NUMBER NAME => $1 * $2
- lex(is);
- ret *= getVariable(_strValue);
+ Token token = lookAhead(is);
+ if (mpark::get_if<double>(&token)) { // NUMBER => $1
+ double ret = mpark::get<double>(lex(is));
+ token = lookAhead(is);
+ if (mpark::get_if<string>(&token)) // NUMBER STRING => $1 * $2
+ ret *= getVariable(mpark::get<string>(lex(is)));
+ return ret;
+ }
+ if (mpark::get_if<string>(&token)) // STRING => getVariable($1)
+ return getVariable(mpark::get<string>(lex(is)));
+ if (char *op = mpark::get_if<char>(&token)) {
+ switch (*op) {
+ case '-': // '-' prim => -$2
+ return -prim(is, true);
+ case '(': { // '(' expr ')' => $2
+ double e = expr(is, true);
+ try {
+ if (mpark::get<char>(lookAhead(is)) != ')')
+ throw CalculatorException("')' expected");
+ }
+ catch (mpark::bad_variant_access &e) {
+ throw CalculatorException("')' expected");
+ }
+ is.get(); // skip processed char
+ return e;
}
- return ret;
- }
- case NAME: { // NAME => getVariable($1)
- lex(is);
- return getVariable(_strValue);
- }
- case '-': // '-' prim => -$2
- return -prim(is, true);
- case '(': { // '(' expr ')' => $2
- double e = expr(is, true);
- if (lookAhead(is) != ')')
- throw CalculatorException("')' expected");
- lex(is);
- return e;
}
- default:
- throw CalculatorException("primary expression expected");
}
+ throw CalculatorException("primary expression expected");
+}
+
+
+/** Returns the value of a previously defined variable. If there
+ * is no variable of the given name, a CalculatorException is thrown.
+ * @param[in] name name of variable
+ * @return assigned value */
+double Calculator::getVariable (const string &name) const {
+ auto it = _variables.find(name);
+ if (it == _variables.end())
+ throw CalculatorException("undefined variable '" + name + "'");
+ return it->second;
}
-/** Determines type of next token without swallowing it. That means
- * the same token will be read again next time. */
-char Calculator::lookAhead (istream &is) {
+/** Determines the type of the next token without swallowing it. That means
+ * the same token will be read again next time. The value of the returned token
+ * object is only set for character tokens and should be considered undefined
+ * for the other types. */
+Calculator::Token Calculator::lookAhead (istream &is) {
is >> ws;
int c = is.peek();
if (is.eof())
- return END;
+ return false;
if (isdigit(c) || c == '.')
- return NUMBER;
+ return double(0);
if (isalpha(c))
- return NAME;
+ return string();
return char(c);
}
-/** Reads next token and returns its type. The token value is either assigned
- * to the object members numValue or strValue depending on the type. The token
- * type is represented by a unique integer. In contrast to method 'lookAhead'
- * lex consumes the read token.
+/** Reads and swallows the next token. The returned Token object is represented by
+ * a variant and thus contains both its type and value.
* @param[in] is next token is read from this stream
- * @return token type */
-char Calculator::lex (istream &is) {
- int tokenType = lookAhead(is);
- switch (tokenType) {
- case NUMBER: {
- string str;
- while (isdigit(is.peek()) || is.peek() == '.')
- str += char(is.get());
- try {
- _numValue = stod(str);
- }
- catch (const exception&) {
- throw CalculatorException("invalid number: "+str);
- }
- break;
+ * @return the read token */
+Calculator::Token Calculator::lex (istream &is) {
+ Token token = lookAhead(is);
+ if (mpark::get_if<char>(&token))
+ is.get(); // token already contains the value from look ahead
+ else if (mpark::get_if<double>(&token)) {
+ string str;
+ while (isdigit(is.peek()) || is.peek() == '.')
+ str += char(is.get());
+ try {
+ token.emplace<double>(stod(str));
}
- case NAME: {
- _strValue.clear();
- while (isalpha(is.peek()))
- _strValue += char(is.get());
- break;
+ catch (const exception&) {
+ throw CalculatorException("invalid number: "+str);
}
- default:
- tokenType = is.get();
}
- return char(tokenType);
-}
-
-
-/** Returns the value of a previously defined variable. If there
- * is no variable of the given name, a CalculatorException is thrown.
- * @param[in] name name of variable
- * @return assigned value */
-double Calculator::getVariable (const string &name) const {
- auto it = _variables.find(name);
- if (it == _variables.end())
- throw CalculatorException("undefined variable '" + name + "'");
- return it->second;
+ else if (mpark::get_if<string>(&token)) {
+ string name;
+ while (isalpha(is.peek()))
+ name += char(is.get());
+ token.emplace<string>(name);
+ }
+ return token;
}
-