| /* | |
| Licensed to the Apache Software Foundation (ASF) under one | |
| or more contributor license agreements. See the NOTICE file | |
| distributed with this work for additional information | |
| regarding copyright ownership. The ASF licenses this file | |
| to you under the Apache License, Version 2.0 (the | |
| "License"); you may not use this file except in compliance | |
| with the License. You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| */ | |
| /** | |
| * @file evaluate.h | |
| * @brief Prefetch formula evaluation (header file). | |
| */ | |
| #include "evaluate.h" | |
| #include <charconv> | |
| #include <sstream> | |
| #include <istream> | |
| #include <iomanip> | |
| #include <cstdint> | |
| #include <cinttypes> | |
| namespace | |
| { | |
| inline int8_t | |
| tonum(char ch) | |
| { | |
| return ch - '0'; | |
| } | |
| inline char | |
| tochar(int8_t ch) | |
| { | |
| return ch + '0'; | |
| } | |
| constexpr StringView const svzero{"0"}; | |
| constexpr char const *const digits = "0123456789"; | |
| inline bool | |
| is_valid_digits(const StringView val) | |
| { | |
| return val.npos != val.find_first_of(digits); | |
| } | |
| // both strings should have all digits | |
| String | |
| add(StringView const lhs, StringView const rhs) | |
| { | |
| String result("0"); | |
| StringView other; | |
| if (lhs.length() < rhs.length()) { | |
| other = lhs; | |
| result.append(rhs); | |
| } else { | |
| other = rhs; | |
| result.append(lhs); | |
| } | |
| bool carry = false; | |
| auto itr = result.rbegin(); | |
| auto ito = other.crbegin(); | |
| while (ito != other.crend()) { | |
| int8_t val = tonum(*itr) + tonum(*ito); | |
| if (carry) { | |
| ++val; | |
| } | |
| if (val < 10) { | |
| carry = false; | |
| } else { | |
| val -= 10; | |
| carry = true; | |
| } | |
| *itr = tochar(val); | |
| ++itr; | |
| ++ito; | |
| } | |
| while (result.rend() != itr && carry) { | |
| int8_t val = tonum(*itr) + 1; | |
| if (val < 10) { | |
| carry = false; | |
| } else { | |
| val -= 10; | |
| carry = true; | |
| } | |
| *itr = tochar(val); | |
| ++itr; | |
| } | |
| return result; | |
| } | |
| String | |
| sub(StringView const lhs, StringView const rhs) | |
| { | |
| String result; | |
| // ensure result length gte rhs length | |
| if (lhs.length() < rhs.length()) { | |
| result.append(rhs.length() - lhs.length(), '0'); | |
| result.append(lhs); | |
| } else { | |
| result = lhs; | |
| } | |
| PrefetchDebug("sub init result: '%s', rhs: '%.*s'", result.c_str(), (int)rhs.length(), rhs.data()); | |
| bool borrow = false; | |
| // top and bottom of subtraction | |
| auto itr = result.rbegin(); | |
| auto itb = rhs.crbegin(); | |
| while (result.rend() != itr && rhs.crend() != itb) { | |
| int8_t val = tonum(*itr) - tonum(*itb); | |
| if (borrow) { | |
| --val; | |
| } | |
| if (val < 0) { | |
| borrow = true; | |
| val += 10; | |
| } else { | |
| borrow = false; | |
| } | |
| *itr = tochar(val); | |
| ++itr; | |
| ++itb; | |
| } | |
| // keep pushing borrow | |
| while (result.rend() != itr && borrow) { | |
| int8_t val = tonum(*itr) - 1; | |
| if (val < 0) { | |
| borrow = true; | |
| val += 10; | |
| } else { | |
| borrow = false; | |
| } | |
| *itr = tochar(val); | |
| ++itr; | |
| } | |
| PrefetchDebug("sub result: '%s', borrow: '%s'", result.c_str(), borrow ? "true" : "false"); | |
| // if result would have been negative. | |
| if (borrow) { | |
| result = std::string{"0"}; | |
| } | |
| return result; | |
| } | |
| String | |
| evaluateBignum(const StringView view) | |
| { | |
| String result("0"); | |
| StringView v = view; | |
| uint32_t fwide = 0; | |
| StringView::size_type pos = v.find_first_of(':'); | |
| if (v.npos != pos) { | |
| std::from_chars(v.begin(), v.begin() + pos, fwide); | |
| PrefetchDebug("statement: '%.*s', formatting length: %" PRIu32, (int)pos, v.data(), fwide); | |
| v = v.substr(pos + 1); | |
| } | |
| pos = v.find_first_of("+-"); | |
| if (v.npos == pos) { | |
| if (is_valid_digits(v)) { | |
| result.assign(v); | |
| } | |
| } else { | |
| StringView vleft = v.substr(0, pos); | |
| if (!is_valid_digits(vleft)) { | |
| vleft = svzero; | |
| } | |
| StringView vrite = v.substr(pos + 1); | |
| if (!is_valid_digits(vrite)) { | |
| vrite = svzero; | |
| } | |
| if ('+' == v[pos]) { | |
| PrefetchDebug("Adding %.*s and %.*s", (int)vleft.length(), vleft.data(), (int)vrite.length(), vrite.data()); | |
| result = add(vleft, vrite); | |
| } else { | |
| PrefetchDebug("Subbing %.*s and %.*s", (int)vleft.length(), vleft.data(), (int)vrite.length(), vrite.data()); | |
| result = sub(vleft, vrite); | |
| } | |
| } | |
| // wipe out leading zeros | |
| while (1 < result.length() && fwide < result.length() && '0' == result.front()) { | |
| result.erase(0, 1); | |
| } | |
| // Left pad out with zeros | |
| if (result.length() < fwide) { | |
| result.insert(0, fwide - result.length(), '0'); | |
| } | |
| return result; | |
| } | |
| } // namespace | |
| /** | |
| * @brief Evaluate a math addition or subtraction expression. | |
| * | |
| * @param v string containing an expression, i.e. "3 + 4" | |
| * @return string containing the result, i.e. "7" | |
| */ | |
| String | |
| evaluate(const StringView view, const EvalPolicy policy) | |
| { | |
| if (view.empty()) { | |
| return String(""); | |
| } | |
| // short circuit | |
| if (policy == EvalPolicy::Bignum) { | |
| return evaluateBignum(view); | |
| } | |
| StringView v = view; | |
| /* Find out if width is specified (hence leading zeros are required if the width is bigger then the result width) */ | |
| String stmt; | |
| uint32_t len = 0; | |
| StringView::size_type pos = v.find_first_of(':'); | |
| if (v.npos != pos) { | |
| stmt.assign(v.substr(0, pos)); | |
| std::istringstream iss(stmt); | |
| iss >> len; | |
| v = v.substr(pos + 1); | |
| } | |
| PrefetchDebug("statement: '%s', formatting length: %" PRIu32, stmt.c_str(), len); | |
| uint64_t result = 0; | |
| pos = v.find_first_of("+-"); | |
| if (v.npos == pos) { | |
| stmt.assign(v.substr(0, pos)); | |
| std::istringstream iss(stmt); | |
| if (policy == EvalPolicy::Overflow64) { | |
| iss >> result; | |
| } else { | |
| uint32_t tmp32; | |
| iss >> tmp32; | |
| result = tmp32; | |
| } | |
| PrefetchDebug("Single-operand expression: %s -> %" PRIu64, stmt.c_str(), result); | |
| } else { | |
| const String leftOperand(v.substr(0, pos)); | |
| std::istringstream liss(leftOperand); | |
| uint64_t a64 = 0; | |
| if (policy == EvalPolicy::Overflow64) { | |
| liss >> a64; | |
| } else { | |
| uint32_t a32; | |
| liss >> a32; | |
| a64 = a32; | |
| } | |
| PrefetchDebug("Left-operand expression: %s -> %" PRIu64, leftOperand.c_str(), a64); | |
| const String rightOperand(v.substr(pos + 1)); | |
| std::istringstream riss(rightOperand); | |
| uint64_t b64 = 0; | |
| if (policy == EvalPolicy::Overflow64) { | |
| riss >> b64; | |
| } else { | |
| uint32_t b32; | |
| riss >> b32; | |
| b64 = b32; | |
| } | |
| PrefetchDebug("Right-operand expression: %s -> %" PRIu64, rightOperand.c_str(), b64); | |
| if ('+' == v[pos]) { | |
| result = a64 + b64; | |
| } else { | |
| if (a64 <= b64) { | |
| result = 0; | |
| } else { | |
| result = a64 - b64; | |
| } | |
| } | |
| } | |
| std::ostringstream convert; | |
| convert << std::setw(len) << std::setfill('0') << result; | |
| PrefetchDebug("evaluation of '%.*s' resulted in '%s'", (int)view.length(), view.data(), convert.str().c_str()); | |
| return convert.str(); | |
| } |