From 059cab71c68a3fbee36f60e8208d4a47d11c6b60 Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Thu, 26 Aug 2021 03:02:29 +0000 Subject: CTAN sync 202108260302 --- dviware/dvisvgm/src/Calculator.cpp | 228 ++++++++++++++++++++----------------- 1 file changed, 122 insertions(+), 106 deletions(-) (limited to 'dviware/dvisvgm/src/Calculator.cpp') 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 ** +** Copyright (C) 2005-2021 Martin Gieseking ** ** ** ** 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(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(&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(&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(&token)) { // NUMBER => $1 + double ret = mpark::get(lex(is)); + token = lookAhead(is); + if (mpark::get_if(&token)) // NUMBER STRING => $1 * $2 + ret *= getVariable(mpark::get(lex(is))); + return ret; + } + if (mpark::get_if(&token)) // STRING => getVariable($1) + return getVariable(mpark::get(lex(is))); + if (char *op = mpark::get_if(&token)) { + switch (*op) { + case '-': // '-' prim => -$2 + return -prim(is, true); + case '(': { // '(' expr ')' => $2 + double e = expr(is, true); + try { + if (mpark::get(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(&token)) + is.get(); // token already contains the value from look ahead + else if (mpark::get_if(&token)) { + string str; + while (isdigit(is.peek()) || is.peek() == '.') + str += char(is.get()); + try { + token.emplace(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(&token)) { + string name; + while (isalpha(is.peek())) + name += char(is.get()); + token.emplace(name); + } + return token; } - -- cgit v1.2.3