diff --git a/packages/xod-tabtest/cpp/formatters.cpp b/cpplib/catch2utils/XStringFormat.inl similarity index 75% rename from packages/xod-tabtest/cpp/formatters.cpp rename to cpplib/catch2utils/XStringFormat.inl index 172c7b7d..9830a3ec 100644 --- a/packages/xod-tabtest/cpp/formatters.cpp +++ b/cpplib/catch2utils/XStringFormat.inl @@ -1,3 +1,14 @@ +/** + * This file makes Catch2 show failed tests properly for the XString type. + * E.G. + * "some" == "something" + * instead of + * {?} == {?} + * + * Catch2 string conversion docs: + * https://github.com/catchorg/Catch2/blob/master/docs/tostring.md + */ + template struct XodStringMaker { static std::string convert(T value) { diff --git a/packages/xod-arduino/platform/formatNumber.h b/packages/xod-arduino/platform/formatNumber.h new file mode 100644 index 00000000..64145fc8 --- /dev/null +++ b/packages/xod-arduino/platform/formatNumber.h @@ -0,0 +1,161 @@ +/*============================================================================= + * + * + * Format Numbers + * + * + =============================================================================*/ + +/** + * Provide `formatNumber` cross-platform number to string converter function. + * + * Taken from here: + * https://github.com/client9/stringencoders/blob/master/src/modp_numtoa.c + * Original function name: `modp_dtoa2`. + * + * Modified: + * - `isnan` instead of tricky comparing and return "NaN" + * - handle Infinity values and return "Inf" or "-Inf" + * - return `OVF` and `-OVF` for numbers bigger than max possible, instead of using `sprintf` + * - use `Number` instead of double + * - if negative number rounds to zero, return just "0" instead of "-0" + * + * This is a replacement of `dtostrf`. + */ + +#ifndef XOD_FORMAT_NUMBER_H +#define XOD_FORMAT_NUMBER_H + +namespace xod { + +/** + * Powers of 10 + * 10^0 to 10^9 + */ +static const Number powers_of_10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, + 10000000, 100000000, 1000000000 }; + +static void strreverse(char* begin, char* end) { + char aux; + while (end > begin) + aux = *end, *end-- = *begin, *begin++ = aux; +}; + +size_t formatNumber(Number value, int prec, char* str) { + if (isnan(value)) { + strcpy(str, "NaN"); + return (size_t)3; + } + + if (isinf(value)) { + bool isNegative = value < 0; + strcpy(str, isNegative ? "-Inf" : "Inf"); + return (size_t)isNegative ? 4 : 3; + } + + /* if input is larger than thres_max return "OVF" */ + const Number thres_max = (Number)(0x7FFFFFFF); + + Number diff = 0.0; + char* wstr = str; + + if (prec < 0) { + prec = 0; + } else if (prec > 9) { + /* precision of >= 10 can lead to overflow errors */ + prec = 9; + } + + /* we'll work in positive values and deal with the + negative sign issue later */ + int neg = 0; + if (value < 0) { + neg = 1; + value = -value; + } + + int whole = (int)value; + Number tmp = (value - whole) * powers_of_10[prec]; + uint32_t frac = (uint32_t)(tmp); + diff = tmp - frac; + + if (diff > 0.5) { + ++frac; + /* handle rollover, e.g. case 0.99 with prec 1 is 1.0 */ + if (frac >= powers_of_10[prec]) { + frac = 0; + ++whole; + } + } else if (diff == 0.5 && prec > 0 && (frac & 1)) { + /* if halfway, round up if odd, OR + if last digit is 0. That last part is strange */ + ++frac; + if (frac >= powers_of_10[prec]) { + frac = 0; + ++whole; + } + } else if (diff == 0.5 && prec == 0 && (whole & 1)) { + ++frac; + if (frac >= powers_of_10[prec]) { + frac = 0; + ++whole; + } + } + + if (value > thres_max) { + if (neg) { + strcpy(str, "-OVF"); + return (size_t)4; + } + strcpy(str, "OVF"); + return (size_t)3; + } + + int has_decimal = 0; + int count = prec; + bool notzero = frac > 0; + + /* Remove ending zeros */ + if (prec > 0) { + while (count > 0 && ((frac % 10) == 0)) { + count--; + frac /= 10; + } + } + + while (count > 0) { + --count; + *wstr++ = (char)(48 + (frac % 10)); + frac /= 10; + has_decimal = 1; + } + + if (frac > 0) { + ++whole; + } + + /* add decimal */ + if (has_decimal) { + *wstr++ = '.'; + } + + notzero = notzero || whole > 0; + + /* do whole part + * Take care of sign conversion + * Number is reversed. + */ + do + *wstr++ = (char)(48 + (whole % 10)); + while (whole /= 10); + + if (neg && notzero) { + *wstr++ = '-'; + } + *wstr = '\0'; + strreverse(str, wstr - 1); + return (size_t)(wstr - str); +} + +} // namespace xod +#endif diff --git a/packages/xod-arduino/platform/runtime.cpp b/packages/xod-arduino/platform/runtime.cpp index 2c0c0c3b..b6a5d29b 100644 --- a/packages/xod-arduino/platform/runtime.cpp +++ b/packages/xod-arduino/platform/runtime.cpp @@ -42,79 +42,7 @@ # define pgm_read_ptr(addr) (*(const void **)(addr)) #endif -//---------------------------------------------------------------------------- -// Compatibilities -//---------------------------------------------------------------------------- - -#if !defined(ARDUINO_ARCH_AVR) && !defined(__DTOSTRF_H_) -/* - * Provide dtostrf function for non-AVR platforms. Although many platforms - * provide a stub many others do not. And the stub is based on `sprintf` - * which doesn’t work with floating point formatters on some platforms - * (e.g. Arduino M0). - * - * This is an implementation based on `fcvt` standard function. Taken here: - * https://forum.arduino.cc/index.php?topic=368720.msg2542614#msg2542614 - */ -char *dtostrf(double val, int width, unsigned int prec, char *sout) { - int decpt, sign, reqd, pad; - const char *s, *e; - char *p; - s = fcvt(val, prec, &decpt, &sign); - if (prec == 0 && decpt == 0) { - s = (*s < '5') ? "0" : "1"; - reqd = 1; - } else { - reqd = strlen(s); - if (reqd > decpt) reqd++; - if (decpt == 0) reqd++; - } - if (sign) reqd++; - p = sout; - e = p + reqd; - pad = width - reqd; - if (pad > 0) { - e += pad; - while (pad-- > 0) *p++ = ' '; - } - if (sign) *p++ = '-'; - if (decpt <= 0 && prec > 0) { - *p++ = '0'; - *p++ = '.'; - e++; - while ( decpt < 0 ) { - decpt++; - *p++ = '0'; - } - } - while (p < e) { - *p++ = *s++; - if (p == e) break; - if (--decpt == 0) *p++ = '.'; - } - if (width < 0) { - pad = (reqd + width) * -1; - while (pad-- > 0) *p++ = ' '; - } - *p = 0; - return sout; -} -#endif - - namespace xod { -//---------------------------------------------------------------------------- -// Type definitions -//---------------------------------------------------------------------------- -#if __SIZEOF_FLOAT__ == 4 -typedef float Number; -#else -typedef double Number; -#endif -typedef bool Logic; -typedef unsigned long TimeMs; -typedef uint8_t DirtyFlags; - //---------------------------------------------------------------------------- // Global variables //---------------------------------------------------------------------------- diff --git a/packages/xod-arduino/platform/types.h b/packages/xod-arduino/platform/types.h new file mode 100644 index 00000000..ac8f63fd --- /dev/null +++ b/packages/xod-arduino/platform/types.h @@ -0,0 +1,17 @@ +/*============================================================================= + * + * + * Basic XOD types + * + * + =============================================================================*/ +namespace xod { +#if __SIZEOF_FLOAT__ == 4 +typedef float Number; +#else +typedef double Number; +#endif +typedef bool Logic; +typedef unsigned long TimeMs; +typedef uint8_t DirtyFlags; +} // namespace xod diff --git a/packages/xod-arduino/src/templates.js b/packages/xod-arduino/src/templates.js index d2bbe17d..7d7010d1 100644 --- a/packages/xod-arduino/src/templates.js +++ b/packages/xod-arduino/src/templates.js @@ -14,6 +14,8 @@ import programTpl from '../platform/program.tpl.cpp'; import preambleH from '../platform/preamble.h'; import listViewsH from '../platform/listViews.h'; import listFuncsH from '../platform/listFuncs.h'; +import typesH from '../platform/types.h'; +import formatNumberH from '../platform/formatNumber.h'; import uartH from '../platform/uart.h'; import memoryH from '../platform/memory.h'; import stlH from '../platform/stl.h'; @@ -306,10 +308,12 @@ export const renderProject = def( preambleH, config, stlH, + typesH, listViewsH, memoryH, uartH, omitLocalIncludes(listFuncsH), + formatNumberH, runtimeCpp, impls, program, diff --git a/packages/xod-arduino/test-cpp/formatNumber.cpp b/packages/xod-arduino/test-cpp/formatNumber.cpp new file mode 100644 index 00000000..2edbc620 --- /dev/null +++ b/packages/xod-arduino/test-cpp/formatNumber.cpp @@ -0,0 +1,60 @@ + +#include "catch.hpp" +#include "../platform/types.h" +#include "../platform/listViews.h" +#include "../platform/listFuncs.h" +#include "../platform/formatNumber.h" +#include + +xod::XStringCString convertAndTest(xod::Number val, unsigned int prec, char* sout) { + xod::formatNumber(val, prec, sout); + return xod::XStringCString(sout); +} + +TEST_CASE("Number to XString conversion", "[list]") { + char str [16]; + + SECTION("Integer numbers") { + REQUIRE(convertAndTest((xod::Number) 0, 0, str) == xod::XStringCString("0")); + REQUIRE(convertAndTest((xod::Number) -1, 1, str) == xod::XStringCString("-1")); + REQUIRE(convertAndTest((xod::Number) 42, 5, str) == xod::XStringCString("42")); + REQUIRE(convertAndTest((xod::Number) -579, 1, str) == xod::XStringCString("-579")); + REQUIRE(convertAndTest((xod::Number) 21474836, 0, str) == xod::XStringCString("21474836")); + REQUIRE(convertAndTest((xod::Number) 21474836, 6, str) == xod::XStringCString("21474836")); + } + SECTION("Numbers with floating point") { + REQUIRE(convertAndTest((xod::Number) 0.25, 0, str) == xod::XStringCString("0")); + REQUIRE(convertAndTest((xod::Number) 0.51, 0, str) == xod::XStringCString("1")); + REQUIRE(convertAndTest((xod::Number) 0.451, 1, str) == xod::XStringCString("0.5")); + REQUIRE(convertAndTest((xod::Number) 0.451, 2, str) == xod::XStringCString("0.45")); + REQUIRE(convertAndTest((xod::Number) 0.7385, 4, str) == xod::XStringCString("0.7385")); + REQUIRE(convertAndTest((xod::Number) 0.73855, 4, str) == xod::XStringCString("0.7386")); + REQUIRE(convertAndTest((xod::Number) 123.456, 0, str) == xod::XStringCString("123")); + REQUIRE(convertAndTest((xod::Number) 123.456, 2, str) == xod::XStringCString("123.46")); + + REQUIRE(convertAndTest((xod::Number) -0.25, 0, str) == xod::XStringCString("0")); + REQUIRE(convertAndTest((xod::Number) -0.5, 0, str) == xod::XStringCString("0")); + REQUIRE(convertAndTest((xod::Number) -0.451, 1, str) == xod::XStringCString("-0.5")); + REQUIRE(convertAndTest((xod::Number) -0.6, 0, str) == xod::XStringCString("-1")); + REQUIRE(convertAndTest((xod::Number) -0.7385, 4, str) == xod::XStringCString("-0.7385")); + REQUIRE(convertAndTest((xod::Number) -0.73855, 4, str) == xod::XStringCString("-0.7386")); + REQUIRE(convertAndTest((xod::Number) -123.456, 0, str) == xod::XStringCString("-123")); + REQUIRE(convertAndTest((xod::Number) -123.456, 2, str) == xod::XStringCString("-123.46")); + } + SECTION("NaNs") { + REQUIRE(convertAndTest((xod::Number) NAN, 0, str) == xod::XStringCString("NaN")); + REQUIRE(convertAndTest((xod::Number) NAN, 2, str) == xod::XStringCString("NaN")); + } + SECTION("Infinities") { + REQUIRE(convertAndTest((xod::Number) INFINITY, 0, str) == xod::XStringCString("Inf")); + REQUIRE(convertAndTest((xod::Number) INFINITY, 5, str) == xod::XStringCString("Inf")); + REQUIRE(convertAndTest((xod::Number) -INFINITY, 0, str) == xod::XStringCString("-Inf")); + REQUIRE(convertAndTest((xod::Number) -INFINITY, 5, str) == xod::XStringCString("-Inf")); + } + SECTION("Overflowing numbers") { + REQUIRE(convertAndTest((xod::Number) 99000000000, 0, str) == xod::XStringCString("OVF")); + REQUIRE(convertAndTest((xod::Number) 99000000000, 2, str) == xod::XStringCString("OVF")); + REQUIRE(convertAndTest((xod::Number) -99000000000, 0, str) == xod::XStringCString("-OVF")); + REQUIRE(convertAndTest((xod::Number) -99000000000, 2, str) == xod::XStringCString("-OVF")); + } +} diff --git a/packages/xod-arduino/tools/test-avr-size.sh b/packages/xod-arduino/tools/test-avr-size.sh index a8bc3896..d4f4f49e 100755 --- a/packages/xod-arduino/tools/test-avr-size.sh +++ b/packages/xod-arduino/tools/test-avr-size.sh @@ -25,7 +25,7 @@ AVR Memory Usage ---------------- Device: atmega328p -Program: 1684 bytes (5.1% Full) +Program: 1626 bytes (5.0% Full) (.text + .data + .bootloader) Data: 38 bytes (1.9% Full) diff --git a/packages/xod-arduino/tools/test-cpp.sh b/packages/xod-arduino/tools/test-cpp.sh index afc5cfd2..09865d6e 100755 --- a/packages/xod-arduino/tools/test-cpp.sh +++ b/packages/xod-arduino/tools/test-cpp.sh @@ -7,12 +7,14 @@ RUNNER=$DIR/run-tests g++ \ -I../../vendor/catch2 \ + -I../../cpplib/catch2utils \ -std=c++11 \ -g \ -O0 \ -o $RUNNER \ $DIR/test.cpp \ - $DIR/list.cpp + $DIR/list.cpp \ + $DIR/formatNumber.cpp if [[ $* == *--leak-check* ]]; then valgrind --leak-check=yes $RUNNER diff --git a/packages/xod-cli/package.json b/packages/xod-cli/package.json index 5e5f73b9..64ccffef 100644 --- a/packages/xod-cli/package.json +++ b/packages/xod-cli/package.json @@ -73,7 +73,8 @@ "build:tabtestWorkspace": "cpx \"../xod-tabtest/workspace/**\" \"./bundle/tabtest-workspace\"", "build:tabtestSrc": "cpx \"../xod-tabtest/cpp/**\" \"./bundle/tabtest-cpp\"", "build:catch2": "cpx \"../../vendor/catch2/**\" \"./bundle/catch2\"", - "build:bundle": "yarn run build:workspace && yarn run build:tabtestWorkspace && yarn run build:tabtestSrc && yarn run build:catch2", + "build:catch2utils": "cpx \"../../cpplib/catch2utils/**\" \"./bundle/catch2utils\"", + "build:bundle": "yarn run build:workspace && yarn run build:tabtestWorkspace && yarn run build:tabtestSrc && yarn run build:catch2 && yarn run build:catch2utils", "build:readme": "oclif-dev readme && sed -i -e 's/lib\\/commands\\//src\\/commands\\//g' README.md", "build:src": "babel src/ -d lib/ --source-maps", "build": "yarn run build:bundle && yarn run build:src", diff --git a/packages/xod-cli/src/commands/tabtest.js b/packages/xod-cli/src/commands/tabtest.js index e016d052..fff47fce 100644 --- a/packages/xod-cli/src/commands/tabtest.js +++ b/packages/xod-cli/src/commands/tabtest.js @@ -17,6 +17,7 @@ import * as myFlags from '../flags'; import { getListr } from '../listr'; import { resolveBundledCatch2Path, + resolveBundledCatch2UtilsPath, resolveBundledTabtestSrcPath, resolveBundledTabtestWorkspacePath, resolveBundledWorkspacePath, @@ -102,6 +103,7 @@ class TabtestCommand extends BaseCommand { allPromises, append(fs.copy(resolveBundledTabtestSrcPath(), ctx.outDir)), append(fs.copy(resolveBundledCatch2Path(), ctx.outDir)), + append(fs.copy(resolveBundledCatch2UtilsPath(), ctx.outDir)), map(([filename, content]) => fs.outputFile(path.join(ctx.outDir, filename), content) ) diff --git a/packages/xod-cli/src/paths.js b/packages/xod-cli/src/paths.js index 09be336b..4c812235 100644 --- a/packages/xod-cli/src/paths.js +++ b/packages/xod-cli/src/paths.js @@ -11,3 +11,6 @@ export const resolveBundledTabtestSrcPath = () => export const resolveBundledCatch2Path = () => path.resolve(__dirname, '..', 'bundle', 'catch2'); + +export const resolveBundledCatch2UtilsPath = () => + path.resolve(__dirname, '..', 'bundle', 'catch2utils'); diff --git a/packages/xod-tabtest/cpp/Arduino.h b/packages/xod-tabtest/cpp/Arduino.h index 0aee3914..5229955c 100644 --- a/packages/xod-tabtest/cpp/Arduino.h +++ b/packages/xod-tabtest/cpp/Arduino.h @@ -2,10 +2,11 @@ #ifndef ARDUINO_H #define ARDUINO_H -#include // for size_t -#include // for uint32_t, etc -#include // for strlen -#include // for fcvt +#include // for min/max +#include // for size_t +#include // for uint32_t, etc +#include // for strlen +#include // to bring `abs` before its redefinition below #include void setup(); @@ -13,6 +14,9 @@ void loop(); #define A0 14 +using ::std::min; +using ::std::max; + // undefine stdlib's abs if encountered // because all platforms' Arduino.h do it #ifdef abs diff --git a/packages/xod-tabtest/src/Tabtest.re b/packages/xod-tabtest/src/Tabtest.re index aa0e67aa..dc7adb22 100644 --- a/packages/xod-tabtest/src/Tabtest.re +++ b/packages/xod-tabtest/src/Tabtest.re @@ -254,7 +254,7 @@ module TestCase = { Cpp.( source([ "#include \"catch.hpp\"", - "#include \"formatters.cpp\"", + "#include ", "", source(nodeAliases), "", diff --git a/workspace/__ardulib__/ESP8266UART/ESP8266UART.h b/workspace/__ardulib__/ESP8266UART/ESP8266UART.h index 6433592a..0b4c5c7a 100644 --- a/workspace/__ardulib__/ESP8266UART/ESP8266UART.h +++ b/workspace/__ardulib__/ESP8266UART/ESP8266UART.h @@ -262,7 +262,7 @@ bool ESP8266::createTCP(const char* addr, uint32_t port) { writeCmd(COMMA_2); print(addr); writeCmd(COMMA_1); - dtostrf(port, 0, 0, _port); + formatNumber(port, 0, _port); println(_port); bool ok = cmdOK(OK, ERROR, 5000); @@ -286,7 +286,7 @@ bool ESP8266::send(char* message) { size_t len = sprintf(message, "%s", message); char reqLen[len]; - dtostrf(len, 0, 0, reqLen); + formatNumber(len, 0, reqLen); println(reqLen); bool prompt = cmdOK(PROMPT, LINK_IS_NOT); diff --git a/workspace/__lib__/xod/core/cast-number-to-string/patch.cpp b/workspace/__lib__/xod/core/cast-number-to-string/patch.cpp index 74affb9b..885e16e1 100644 --- a/workspace/__lib__/xod/core/cast-number-to-string/patch.cpp +++ b/workspace/__lib__/xod/core/cast-number-to-string/patch.cpp @@ -12,6 +12,6 @@ struct State { void evaluate(Context ctx) { auto state = getState(ctx); auto num = getValue(ctx); - dtostrf(num, 0, 2, state->str); + formatNumber(num, 2, state->str); emitValue(ctx, XString(&state->view)); } diff --git a/workspace/__lib__/xod/core/cast-to-string(number)/patch.cpp b/workspace/__lib__/xod/core/cast-to-string(number)/patch.cpp index 74affb9b..885e16e1 100644 --- a/workspace/__lib__/xod/core/cast-to-string(number)/patch.cpp +++ b/workspace/__lib__/xod/core/cast-to-string(number)/patch.cpp @@ -12,6 +12,6 @@ struct State { void evaluate(Context ctx) { auto state = getState(ctx); auto num = getValue(ctx); - dtostrf(num, 0, 2, state->str); + formatNumber(num, 2, state->str); emitValue(ctx, XString(&state->view)); } diff --git a/workspace/__lib__/xod/core/format-number/patch.cpp b/workspace/__lib__/xod/core/format-number/patch.cpp index 82922f44..c963d1ea 100644 --- a/workspace/__lib__/xod/core/format-number/patch.cpp +++ b/workspace/__lib__/xod/core/format-number/patch.cpp @@ -13,6 +13,6 @@ void evaluate(Context ctx) { auto state = getState(ctx); auto num = getValue(ctx); auto dig = getValue(ctx); - dtostrf(num, 0, dig, state->str); + formatNumber(num, dig, state->str); emitValue(ctx, XString(&state->view)); } diff --git a/workspace/blink/__fixtures__/arduino.cpp b/workspace/blink/__fixtures__/arduino.cpp index f4a355e8..800f454b 100644 --- a/workspace/blink/__fixtures__/arduino.cpp +++ b/workspace/blink/__fixtures__/arduino.cpp @@ -56,6 +56,24 @@ typename remove_reference::type&& move(T&& a) { } // namespace std } // namespace xod +/*============================================================================= + * + * + * Basic XOD types + * + * + =============================================================================*/ +namespace xod { +#if __SIZEOF_FLOAT__ == 4 +typedef float Number; +#else +typedef double Number; +#endif +typedef bool Logic; +typedef unsigned long TimeMs; +typedef uint8_t DirtyFlags; +} // namespace xod + /*============================================================================= * * @@ -573,10 +591,176 @@ template bool equal(List lhs, List rhs) { return !lhsIt && !rhsIt; } +template bool operator == (List lhs, List rhs) { + return equal(lhs, rhs); +} + } // namespace xod #endif +/*============================================================================= + * + * + * Format Numbers + * + * + =============================================================================*/ + +/** + * Provide `formatNumber` cross-platform number to string converter function. + * + * Taken from here: + * https://github.com/client9/stringencoders/blob/master/src/modp_numtoa.c + * Original function name: `modp_dtoa2`. + * + * Modified: + * - `isnan` instead of tricky comparing and return "NaN" + * - handle Infinity values and return "Inf" or "-Inf" + * - return `OVF` and `-OVF` for numbers bigger than max possible, instead of using `sprintf` + * - use `Number` instead of double + * - if negative number rounds to zero, return just "0" instead of "-0" + * + * This is a replacement of `dtostrf`. + */ + +#ifndef XOD_FORMAT_NUMBER_H +#define XOD_FORMAT_NUMBER_H + +namespace xod { + +/** + * Powers of 10 + * 10^0 to 10^9 + */ +static const Number powers_of_10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, + 10000000, 100000000, 1000000000 }; + +static void strreverse(char* begin, char* end) { + char aux; + while (end > begin) + aux = *end, *end-- = *begin, *begin++ = aux; +}; + +size_t formatNumber(Number value, int prec, char* str) { + if (isnan(value)) { + strcpy(str, "NaN"); + return (size_t)3; + } + + if (isinf(value)) { + bool isNegative = value < 0; + strcpy(str, isNegative ? "-Inf" : "Inf"); + return (size_t)isNegative ? 4 : 3; + } + + /* if input is larger than thres_max return "OVF" */ + const Number thres_max = (Number)(0x7FFFFFFF); + + Number diff = 0.0; + char* wstr = str; + + if (prec < 0) { + prec = 0; + } else if (prec > 9) { + /* precision of >= 10 can lead to overflow errors */ + prec = 9; + } + + /* we'll work in positive values and deal with the + negative sign issue later */ + int neg = 0; + if (value < 0) { + neg = 1; + value = -value; + } + + int whole = (int)value; + Number tmp = (value - whole) * powers_of_10[prec]; + uint32_t frac = (uint32_t)(tmp); + diff = tmp - frac; + + if (diff > 0.5) { + ++frac; + /* handle rollover, e.g. case 0.99 with prec 1 is 1.0 */ + if (frac >= powers_of_10[prec]) { + frac = 0; + ++whole; + } + } else if (diff == 0.5 && prec > 0 && (frac & 1)) { + /* if halfway, round up if odd, OR + if last digit is 0. That last part is strange */ + ++frac; + if (frac >= powers_of_10[prec]) { + frac = 0; + ++whole; + } + } else if (diff == 0.5 && prec == 0 && (whole & 1)) { + ++frac; + if (frac >= powers_of_10[prec]) { + frac = 0; + ++whole; + } + } + + if (value > thres_max) { + if (neg) { + strcpy(str, "-OVF"); + return (size_t)4; + } + strcpy(str, "OVF"); + return (size_t)3; + } + + int has_decimal = 0; + int count = prec; + bool notzero = frac > 0; + + /* Remove ending zeros */ + if (prec > 0) { + while (count > 0 && ((frac % 10) == 0)) { + count--; + frac /= 10; + } + } + + while (count > 0) { + --count; + *wstr++ = (char)(48 + (frac % 10)); + frac /= 10; + has_decimal = 1; + } + + if (frac > 0) { + ++whole; + } + + /* add decimal */ + if (has_decimal) { + *wstr++ = '.'; + } + + notzero = notzero || whole > 0; + + /* do whole part + * Take care of sign conversion + * Number is reversed. + */ + do + *wstr++ = (char)(48 + (whole % 10)); + while (whole /= 10); + + if (neg && notzero) { + *wstr++ = '-'; + } + *wstr = '\0'; + strreverse(str, wstr - 1); + return (size_t)(wstr - str); +} + +} // namespace xod +#endif + /*============================================================================= * @@ -621,79 +805,7 @@ template bool equal(List lhs, List rhs) { # define pgm_read_ptr(addr) (*(const void **)(addr)) #endif -//---------------------------------------------------------------------------- -// Compatibilities -//---------------------------------------------------------------------------- - -#if !defined(ARDUINO_ARCH_AVR) && !defined(__DTOSTRF_H_) -/* - * Provide dtostrf function for non-AVR platforms. Although many platforms - * provide a stub many others do not. And the stub is based on `sprintf` - * which doesn’t work with floating point formatters on some platforms - * (e.g. Arduino M0). - * - * This is an implementation based on `fcvt` standard function. Taken here: - * https://forum.arduino.cc/index.php?topic=368720.msg2542614#msg2542614 - */ -char *dtostrf(double val, int width, unsigned int prec, char *sout) { - int decpt, sign, reqd, pad; - const char *s, *e; - char *p; - s = fcvt(val, prec, &decpt, &sign); - if (prec == 0 && decpt == 0) { - s = (*s < '5') ? "0" : "1"; - reqd = 1; - } else { - reqd = strlen(s); - if (reqd > decpt) reqd++; - if (decpt == 0) reqd++; - } - if (sign) reqd++; - p = sout; - e = p + reqd; - pad = width - reqd; - if (pad > 0) { - e += pad; - while (pad-- > 0) *p++ = ' '; - } - if (sign) *p++ = '-'; - if (decpt <= 0 && prec > 0) { - *p++ = '0'; - *p++ = '.'; - e++; - while ( decpt < 0 ) { - decpt++; - *p++ = '0'; - } - } - while (p < e) { - *p++ = *s++; - if (p == e) break; - if (--decpt == 0) *p++ = '.'; - } - if (width < 0) { - pad = (reqd + width) * -1; - while (pad-- > 0) *p++ = ' '; - } - *p = 0; - return sout; -} -#endif - - namespace xod { -//---------------------------------------------------------------------------- -// Type definitions -//---------------------------------------------------------------------------- -#if __SIZEOF_FLOAT__ == 4 -typedef float Number; -#else -typedef double Number; -#endif -typedef bool Logic; -typedef unsigned long TimeMs; -typedef uint8_t DirtyFlags; - //---------------------------------------------------------------------------- // Global variables //---------------------------------------------------------------------------- diff --git a/workspace/blink/__fixtures__/firmware.bin b/workspace/blink/__fixtures__/firmware.bin index e7f9198d..b8ae2cfa 100644 Binary files a/workspace/blink/__fixtures__/firmware.bin and b/workspace/blink/__fixtures__/firmware.bin differ diff --git a/workspace/blink/__fixtures__/firmware.hex b/workspace/blink/__fixtures__/firmware.hex index 1ac449a3..62017cfd 100644 --- a/workspace/blink/__fixtures__/firmware.hex +++ b/workspace/blink/__fixtures__/firmware.hex @@ -2,7 +2,7 @@ :100010000C9479000C9479000C9479000C9479007C :100020000C9479000C9479000C9479000C9479006C :100030000C9479000C9479000C9479000C9479005C -:100040000C9474020C9479000C9479000C9479004F +:100040000C9460020C9479000C9479000C94790063 :100050000C9479000C9479000C9479000C9479003C :100060000C9479000C947900000000002400270013 :100070002A0000000000250028002B0004040404CE @@ -10,97 +10,95 @@ :10009000010204081020408001020408102001021F :1000A00004081020000000080002010000030407FB :1000B000000000000000000011241FBECFEFD8E0B8 -:1000C000DEBFCDBF11E0A0E0B1E0EAE6F6E002C09D +:1000C000DEBFCDBF11E0A0E0B1E0E2E4F6E002C0A7 :1000D00005900D92A831B107D9F721E0A8E1B1E070 -:1000E00001C01D92A632B207E1F70E94BE020C9435 -:1000F00033030C9400003FB7F89480912201909153 +:1000E00001C01D92A632B207E1F70E94AA020C9449 +:1000F0001F030C9400003FB7F89480912201909167 :100100002301A0912401B091250126B5A89B05C02B :100110002F3F19F00196A11DB11D3FBFBA2FA92F86 :10012000982F8827820F911DA11DB11DBC01CD0103 :1001300042E0660F771F881F991F4A95D1F70895EF :1001400080E090E0892B49F080E090E0892B29F055 :100150000E94000081110C94000008950F931F93DA -:100160002FB7F89480911E0190911F01A09120015A -:10017000B09121012FBF8093180190931901A09392 -:100180001A01B0931B01409112015091130160912B -:10019000140170911501411551056105710531F08A -:1001A00021E0481759076A077B0708F020E0809193 -:1001B000170181FB992790F9292B20FB81F9809366 -:1001C000170140910B0150910C0160910D0170914C -:1001D0000E01411551056105710571F031E0809105 -:1001E000180190911901A0911A01B0911B014817B3 -:1001F00059076A077B0708F030E08091100181FB06 -:10020000992790F9932B90FB81F980931001222379 -:10021000E9F081E08093160180911701816080935D -:100220001701009118011091190120911A013091C4 -:100230001B010093120110931301209314013093BA -:100240001501809106018460809306019923F1F1E4 -:10025000E0911C01009118011091190120911A01DF -:1002600030911B01D901C80186509F4FAF4FBF4F3E -:10027000411551056105710531F0401751076207BD -:10028000730708F415C1EE23A1F0409107015091C6 -:1002900008016091090170910A0140175107620736 -:1002A000730708F4EBC084179507A607B70708F48F -:1002B000E5C09091100191708091020181FB22278D -:1002C00020F9922B90FB81F9809302018091020129 -:1002D00081FF20C0909101018091100180FF03C037 -:1002E00081E0892701C080E0891739F080930101FE -:1002F00080910201816080930201909102019170CE -:100300008091060182FB222720F9922B90FB82F933 -:10031000809306018091060182FF6CC0609101010B -:100320008091170180FF66C04DE950E0FA01749199 -:1003300089E890E0FC012491222399F030E0220F1B -:10034000331FF901E859FF4FA591B4912E583F4F43 -:10035000F901059114912FB7F8943C91732B7C937C -:100360002FBFE1EBF0E02491FA014491FC0194915C -:10037000992309F43CC0222339F1233091F038F459 -:100380002130A9F0223001F584B58F7D12C02730CD -:1003900091F02830A1F02430B9F4809180008F7D55 -:1003A00003C0809180008F77809380000DC084B55A -:1003B0008F7784BD09C08091B0008F7703C0809192 -:1003C000B0008F7D8093B000E92FF0E0EE0FFF1FAB -:1003D000EE58FF4FA591B4918FB7F8949C9161119D -:1003E00003C04095492301C0492B4C938FBF81E046 -:1003F00080930401109217011092100110920201D3 -:10040000109206018091120190911301A0911401A4 -:10041000B09115010097A105B10569F0409118014F -:100420005091190160911A0170911B018417950771 -:10043000A607B707A0F180910B0190910C01A09144 -:100440000D01B0910E010097A105B10509F449C055 -:10045000409118015091190160911A0170911B018E -:1004600084179507A607B707E0F510920B011092C5 -:100470000C0110920D0110920E0133C08093070100 -:1004800090930801A0930901B0930A0180930B0196 -:1004900090930C01A0930D01B0930E010ACF10921E -:1004A0001201109213011092140110921501C3CF82 -:1004B00041E040930F0140911001416040931001D1 -:1004C0008093070190930801A0930901B0930A015A -:1004D00080930B0190930C01A0930D01B0930E013A -:1004E000D2CE1F910F9108951F920F920FB60F92C7 -:1004F00011242F933F938F939F93AF93BF9380913A -:100500001E0190911F01A0912001B0912101309115 -:100510001D0123E0230F2D3720F40196A11DB11DED -:1005200005C026E8230F0296A11DB11D20931D01D1 -:1005300080931E0190931F01A0932001B09321018D -:100540008091220190912301A0912401B091250175 -:100550000196A11DB11D8093220190932301A093C8 -:100560002401B0932501BF91AF919F918F913F914D -:100570002F910F900FBE0F901F901895789484B50F -:10058000826084BD84B5816084BD85B5826085BD8F -:1005900085B5816085BD80916E00816080936E001D -:1005A000109281008091810082608093810080910F -:1005B00081008160809381008091800081608093C0 -:1005C00080008091B10084608093B1008091B00080 -:1005D00081608093B00080917A00846080937A007B -:1005E00080917A00826080937A0080917A008160A5 -:1005F00080937A0080917A00806880937A001092CC -:10060000C1000E947B004B015C0184E6C82ED12C06 -:10061000E12CF12C0E947B00DC01CB0188199909A7 -:10062000AA09BB09883E9340A105B10558F021E015 -:10063000C21AD108E108F10888EE880E83E0981EFE -:10064000A11CB11CC114D104E104F10419F781E02B -:1006500080931C010E94AE0010921C010E94AE000B -:0A0660000E94A000FBCFF894FFCF2A -:10066A000000030000000400000000000000000079 -:08067A00020000000000000274 +:10016000FC01448155816681778141155105610506 +:10017000710571F081E00091180110911901209131 +:100180001A0130911B01401751076207730708F0ED +:1001900080E01F910F9108958F929F92AF92BF922E +:1001A000CF92DF92EF92FF92CF93DF938FB7F894C5 +:1001B00080901E0190901F01A0902001B09021011D +:1001C0008FBF8092180190921901A0921A01B092EB +:1001D0001B01409112015091130160911401709123 +:1001E0001501411551056105710531F081E0481592 +:1001F00059056A057B0508F080E0C0911701C1FB35 +:10020000DD27D0F9D82BD0FBC1F9C093170187E0C7 +:1002100091E00E94AE009091100191FB222720F9FD +:10022000822B80FB91F990931001DD2399F091E0EE +:1002300090931601C160C0931701809212019092B1 +:100240001301A0921401B0921501909106019460DF +:1002500090930601882391F1C0911C0175016401FE +:100260008AEFC80ED11CE11CF11C87E091E00E94CE +:10027000AE008111E7C0CC23A1F0809107019091DD +:100280000801A0910901B0910A0188159905AA05F4 +:10029000BB0508F4F0C0C816D906EA06FB0608F448 +:1002A000EAC08091020181FB222720F99091100180 +:1002B0009170922B90FB81F9809302018091020151 +:1002C00081FF20C0909101018091100180FF03C047 +:1002D00081E0892701C080E0981739F080930101FF +:1002E000809102018160809302018091060182FB6E +:1002F000222720F9909102019170922B90FB82F9B4 +:10030000809306018091060182FF6CC0609101011B +:100310008091170180FF66C04DE950E0FA017491A9 +:1003200089E890E0FC012491222399F030E0220F2B +:10033000331FF901E859FF4FA591B491F901EE5827 +:10034000FF4F259134913FB7F8942C91272B2C9394 +:100350003FBFE1EBF0E02491FA014491FC0194915C +:10036000992309F43CC0222339F1233091F038F469 +:100370002130A9F0223001F584B58F7D12C02730DD +:1003800091F02830A1F02430B9F4809180008F7D65 +:1003900003C0809180008F77809380000DC084B56A +:1003A0008F7784BD09C08091B0008F7703C08091A2 +:1003B000B0008F7D8093B000E92FF0E0EE0FFF1FBB +:1003C000EE58FF4FA591B4919FB7F8948C916111AD +:1003D00003C04095482301C0482B4C939FBF81E048 +:1003E00080930401109217011092100110920201E3 +:1003F000109206018091120190911301A0911401B5 +:10040000B09115010097A105B10569F0409118015F +:100410005091190160911A0170911B018417950781 +:10042000A607B707C8F187E091E00E94AE008823D5 +:10043000E1F110920B0110920C0110920D0110923B +:100440000E0133C081E080930F0180911001816023 +:1004500080931001C0920701D0920801E092090137 +:10046000F0920A01C0920B01D0920C01E0920D01B2 +:10047000F0920E0100CFC0920701D0920801E092E5 +:100480000901F0920A01C0920B01D0920C01E09296 +:100490000D01F0920E0105CF10921201109213017E +:1004A0001092140110921501BECFDF91CF91FF90F1 +:1004B000EF90DF90CF90BF90AF909F908F90089576 +:1004C0001F920F920FB60F9211242F933F938F9389 +:1004D0009F93AF93BF9380911E0190911F01A091B4 +:1004E0002001B091210130911D0123E0230F2D3710 +:1004F00020F40196A11DB11D05C026E8230F029628 +:10050000A11DB11D20931D0180931E0190931F0119 +:10051000A0932001B09321018091220190912301A9 +:10052000A0912401B09125010196A11DB11D8093D8 +:10053000220190932301A0932401B0932501BF9140 +:10054000AF919F918F913F912F910F900FBE0F9080 +:100550001F901895789484B5826084BD84B58160BD +:1005600084BD85B5826085BD85B5816085BD80917E +:100570006E00816080936E001092810080918100F6 +:1005800082608093810080918100816080938100EE +:10059000809180008160809380008091B1008460B0 +:1005A0008093B1008091B00081608093B000809111 +:1005B0007A00846080937A0080917A0082608093D0 +:1005C0007A0080917A00816080937A0080917A002D +:1005D000806880937A001092C1000E947B004B01DA +:1005E0005C0184E6C82ED12CE12CF12C0E947B000A +:1005F000DC01CB0188199909AA09BB09883E9340FF +:10060000A105B10598F321E0C21AD108E108F1086B +:1006100088EE880E83E0981EA11CB11CC114D10481 +:10062000E104F10419F781E080931C010E94CC00E1 +:1006300010921C010E94CC000E94A000FBCFF894F5 +:02064000FFCFEA +:1006420000000300000004000000000000000000A1 +:0806520002000000000000029C :00000001FF diff --git a/workspace/count-with-feedback-loops/__fixtures__/arduino.cpp b/workspace/count-with-feedback-loops/__fixtures__/arduino.cpp index a2ea156a..41e6b4b6 100644 --- a/workspace/count-with-feedback-loops/__fixtures__/arduino.cpp +++ b/workspace/count-with-feedback-loops/__fixtures__/arduino.cpp @@ -56,6 +56,24 @@ typename remove_reference::type&& move(T&& a) { } // namespace std } // namespace xod +/*============================================================================= + * + * + * Basic XOD types + * + * + =============================================================================*/ +namespace xod { +#if __SIZEOF_FLOAT__ == 4 +typedef float Number; +#else +typedef double Number; +#endif +typedef bool Logic; +typedef unsigned long TimeMs; +typedef uint8_t DirtyFlags; +} // namespace xod + /*============================================================================= * * @@ -573,10 +591,176 @@ template bool equal(List lhs, List rhs) { return !lhsIt && !rhsIt; } +template bool operator == (List lhs, List rhs) { + return equal(lhs, rhs); +} + } // namespace xod #endif +/*============================================================================= + * + * + * Format Numbers + * + * + =============================================================================*/ + +/** + * Provide `formatNumber` cross-platform number to string converter function. + * + * Taken from here: + * https://github.com/client9/stringencoders/blob/master/src/modp_numtoa.c + * Original function name: `modp_dtoa2`. + * + * Modified: + * - `isnan` instead of tricky comparing and return "NaN" + * - handle Infinity values and return "Inf" or "-Inf" + * - return `OVF` and `-OVF` for numbers bigger than max possible, instead of using `sprintf` + * - use `Number` instead of double + * - if negative number rounds to zero, return just "0" instead of "-0" + * + * This is a replacement of `dtostrf`. + */ + +#ifndef XOD_FORMAT_NUMBER_H +#define XOD_FORMAT_NUMBER_H + +namespace xod { + +/** + * Powers of 10 + * 10^0 to 10^9 + */ +static const Number powers_of_10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, + 10000000, 100000000, 1000000000 }; + +static void strreverse(char* begin, char* end) { + char aux; + while (end > begin) + aux = *end, *end-- = *begin, *begin++ = aux; +}; + +size_t formatNumber(Number value, int prec, char* str) { + if (isnan(value)) { + strcpy(str, "NaN"); + return (size_t)3; + } + + if (isinf(value)) { + bool isNegative = value < 0; + strcpy(str, isNegative ? "-Inf" : "Inf"); + return (size_t)isNegative ? 4 : 3; + } + + /* if input is larger than thres_max return "OVF" */ + const Number thres_max = (Number)(0x7FFFFFFF); + + Number diff = 0.0; + char* wstr = str; + + if (prec < 0) { + prec = 0; + } else if (prec > 9) { + /* precision of >= 10 can lead to overflow errors */ + prec = 9; + } + + /* we'll work in positive values and deal with the + negative sign issue later */ + int neg = 0; + if (value < 0) { + neg = 1; + value = -value; + } + + int whole = (int)value; + Number tmp = (value - whole) * powers_of_10[prec]; + uint32_t frac = (uint32_t)(tmp); + diff = tmp - frac; + + if (diff > 0.5) { + ++frac; + /* handle rollover, e.g. case 0.99 with prec 1 is 1.0 */ + if (frac >= powers_of_10[prec]) { + frac = 0; + ++whole; + } + } else if (diff == 0.5 && prec > 0 && (frac & 1)) { + /* if halfway, round up if odd, OR + if last digit is 0. That last part is strange */ + ++frac; + if (frac >= powers_of_10[prec]) { + frac = 0; + ++whole; + } + } else if (diff == 0.5 && prec == 0 && (whole & 1)) { + ++frac; + if (frac >= powers_of_10[prec]) { + frac = 0; + ++whole; + } + } + + if (value > thres_max) { + if (neg) { + strcpy(str, "-OVF"); + return (size_t)4; + } + strcpy(str, "OVF"); + return (size_t)3; + } + + int has_decimal = 0; + int count = prec; + bool notzero = frac > 0; + + /* Remove ending zeros */ + if (prec > 0) { + while (count > 0 && ((frac % 10) == 0)) { + count--; + frac /= 10; + } + } + + while (count > 0) { + --count; + *wstr++ = (char)(48 + (frac % 10)); + frac /= 10; + has_decimal = 1; + } + + if (frac > 0) { + ++whole; + } + + /* add decimal */ + if (has_decimal) { + *wstr++ = '.'; + } + + notzero = notzero || whole > 0; + + /* do whole part + * Take care of sign conversion + * Number is reversed. + */ + do + *wstr++ = (char)(48 + (whole % 10)); + while (whole /= 10); + + if (neg && notzero) { + *wstr++ = '-'; + } + *wstr = '\0'; + strreverse(str, wstr - 1); + return (size_t)(wstr - str); +} + +} // namespace xod +#endif + /*============================================================================= * @@ -621,79 +805,7 @@ template bool equal(List lhs, List rhs) { # define pgm_read_ptr(addr) (*(const void **)(addr)) #endif -//---------------------------------------------------------------------------- -// Compatibilities -//---------------------------------------------------------------------------- - -#if !defined(ARDUINO_ARCH_AVR) && !defined(__DTOSTRF_H_) -/* - * Provide dtostrf function for non-AVR platforms. Although many platforms - * provide a stub many others do not. And the stub is based on `sprintf` - * which doesn’t work with floating point formatters on some platforms - * (e.g. Arduino M0). - * - * This is an implementation based on `fcvt` standard function. Taken here: - * https://forum.arduino.cc/index.php?topic=368720.msg2542614#msg2542614 - */ -char *dtostrf(double val, int width, unsigned int prec, char *sout) { - int decpt, sign, reqd, pad; - const char *s, *e; - char *p; - s = fcvt(val, prec, &decpt, &sign); - if (prec == 0 && decpt == 0) { - s = (*s < '5') ? "0" : "1"; - reqd = 1; - } else { - reqd = strlen(s); - if (reqd > decpt) reqd++; - if (decpt == 0) reqd++; - } - if (sign) reqd++; - p = sout; - e = p + reqd; - pad = width - reqd; - if (pad > 0) { - e += pad; - while (pad-- > 0) *p++ = ' '; - } - if (sign) *p++ = '-'; - if (decpt <= 0 && prec > 0) { - *p++ = '0'; - *p++ = '.'; - e++; - while ( decpt < 0 ) { - decpt++; - *p++ = '0'; - } - } - while (p < e) { - *p++ = *s++; - if (p == e) break; - if (--decpt == 0) *p++ = '.'; - } - if (width < 0) { - pad = (reqd + width) * -1; - while (pad-- > 0) *p++ = ' '; - } - *p = 0; - return sout; -} -#endif - - namespace xod { -//---------------------------------------------------------------------------- -// Type definitions -//---------------------------------------------------------------------------- -#if __SIZEOF_FLOAT__ == 4 -typedef float Number; -#else -typedef double Number; -#endif -typedef bool Logic; -typedef unsigned long TimeMs; -typedef uint8_t DirtyFlags; - //---------------------------------------------------------------------------- // Global variables //---------------------------------------------------------------------------- @@ -1685,7 +1797,7 @@ State* getState(Context ctx) { void evaluate(Context ctx) { auto state = getState(ctx); auto num = getValue(ctx); - dtostrf(num, 0, 2, state->str); + formatNumber(num, 2, state->str); emitValue(ctx, XString(&state->view)); } diff --git a/workspace/lcd-time/__fixtures__/arduino.cpp b/workspace/lcd-time/__fixtures__/arduino.cpp index 6374eb12..941c1722 100644 --- a/workspace/lcd-time/__fixtures__/arduino.cpp +++ b/workspace/lcd-time/__fixtures__/arduino.cpp @@ -56,6 +56,24 @@ typename remove_reference::type&& move(T&& a) { } // namespace std } // namespace xod +/*============================================================================= + * + * + * Basic XOD types + * + * + =============================================================================*/ +namespace xod { +#if __SIZEOF_FLOAT__ == 4 +typedef float Number; +#else +typedef double Number; +#endif +typedef bool Logic; +typedef unsigned long TimeMs; +typedef uint8_t DirtyFlags; +} // namespace xod + /*============================================================================= * * @@ -573,10 +591,176 @@ template bool equal(List lhs, List rhs) { return !lhsIt && !rhsIt; } +template bool operator == (List lhs, List rhs) { + return equal(lhs, rhs); +} + } // namespace xod #endif +/*============================================================================= + * + * + * Format Numbers + * + * + =============================================================================*/ + +/** + * Provide `formatNumber` cross-platform number to string converter function. + * + * Taken from here: + * https://github.com/client9/stringencoders/blob/master/src/modp_numtoa.c + * Original function name: `modp_dtoa2`. + * + * Modified: + * - `isnan` instead of tricky comparing and return "NaN" + * - handle Infinity values and return "Inf" or "-Inf" + * - return `OVF` and `-OVF` for numbers bigger than max possible, instead of using `sprintf` + * - use `Number` instead of double + * - if negative number rounds to zero, return just "0" instead of "-0" + * + * This is a replacement of `dtostrf`. + */ + +#ifndef XOD_FORMAT_NUMBER_H +#define XOD_FORMAT_NUMBER_H + +namespace xod { + +/** + * Powers of 10 + * 10^0 to 10^9 + */ +static const Number powers_of_10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, + 10000000, 100000000, 1000000000 }; + +static void strreverse(char* begin, char* end) { + char aux; + while (end > begin) + aux = *end, *end-- = *begin, *begin++ = aux; +}; + +size_t formatNumber(Number value, int prec, char* str) { + if (isnan(value)) { + strcpy(str, "NaN"); + return (size_t)3; + } + + if (isinf(value)) { + bool isNegative = value < 0; + strcpy(str, isNegative ? "-Inf" : "Inf"); + return (size_t)isNegative ? 4 : 3; + } + + /* if input is larger than thres_max return "OVF" */ + const Number thres_max = (Number)(0x7FFFFFFF); + + Number diff = 0.0; + char* wstr = str; + + if (prec < 0) { + prec = 0; + } else if (prec > 9) { + /* precision of >= 10 can lead to overflow errors */ + prec = 9; + } + + /* we'll work in positive values and deal with the + negative sign issue later */ + int neg = 0; + if (value < 0) { + neg = 1; + value = -value; + } + + int whole = (int)value; + Number tmp = (value - whole) * powers_of_10[prec]; + uint32_t frac = (uint32_t)(tmp); + diff = tmp - frac; + + if (diff > 0.5) { + ++frac; + /* handle rollover, e.g. case 0.99 with prec 1 is 1.0 */ + if (frac >= powers_of_10[prec]) { + frac = 0; + ++whole; + } + } else if (diff == 0.5 && prec > 0 && (frac & 1)) { + /* if halfway, round up if odd, OR + if last digit is 0. That last part is strange */ + ++frac; + if (frac >= powers_of_10[prec]) { + frac = 0; + ++whole; + } + } else if (diff == 0.5 && prec == 0 && (whole & 1)) { + ++frac; + if (frac >= powers_of_10[prec]) { + frac = 0; + ++whole; + } + } + + if (value > thres_max) { + if (neg) { + strcpy(str, "-OVF"); + return (size_t)4; + } + strcpy(str, "OVF"); + return (size_t)3; + } + + int has_decimal = 0; + int count = prec; + bool notzero = frac > 0; + + /* Remove ending zeros */ + if (prec > 0) { + while (count > 0 && ((frac % 10) == 0)) { + count--; + frac /= 10; + } + } + + while (count > 0) { + --count; + *wstr++ = (char)(48 + (frac % 10)); + frac /= 10; + has_decimal = 1; + } + + if (frac > 0) { + ++whole; + } + + /* add decimal */ + if (has_decimal) { + *wstr++ = '.'; + } + + notzero = notzero || whole > 0; + + /* do whole part + * Take care of sign conversion + * Number is reversed. + */ + do + *wstr++ = (char)(48 + (whole % 10)); + while (whole /= 10); + + if (neg && notzero) { + *wstr++ = '-'; + } + *wstr = '\0'; + strreverse(str, wstr - 1); + return (size_t)(wstr - str); +} + +} // namespace xod +#endif + /*============================================================================= * @@ -621,79 +805,7 @@ template bool equal(List lhs, List rhs) { # define pgm_read_ptr(addr) (*(const void **)(addr)) #endif -//---------------------------------------------------------------------------- -// Compatibilities -//---------------------------------------------------------------------------- - -#if !defined(ARDUINO_ARCH_AVR) && !defined(__DTOSTRF_H_) -/* - * Provide dtostrf function for non-AVR platforms. Although many platforms - * provide a stub many others do not. And the stub is based on `sprintf` - * which doesn’t work with floating point formatters on some platforms - * (e.g. Arduino M0). - * - * This is an implementation based on `fcvt` standard function. Taken here: - * https://forum.arduino.cc/index.php?topic=368720.msg2542614#msg2542614 - */ -char *dtostrf(double val, int width, unsigned int prec, char *sout) { - int decpt, sign, reqd, pad; - const char *s, *e; - char *p; - s = fcvt(val, prec, &decpt, &sign); - if (prec == 0 && decpt == 0) { - s = (*s < '5') ? "0" : "1"; - reqd = 1; - } else { - reqd = strlen(s); - if (reqd > decpt) reqd++; - if (decpt == 0) reqd++; - } - if (sign) reqd++; - p = sout; - e = p + reqd; - pad = width - reqd; - if (pad > 0) { - e += pad; - while (pad-- > 0) *p++ = ' '; - } - if (sign) *p++ = '-'; - if (decpt <= 0 && prec > 0) { - *p++ = '0'; - *p++ = '.'; - e++; - while ( decpt < 0 ) { - decpt++; - *p++ = '0'; - } - } - while (p < e) { - *p++ = *s++; - if (p == e) break; - if (--decpt == 0) *p++ = '.'; - } - if (width < 0) { - pad = (reqd + width) * -1; - while (pad-- > 0) *p++ = ' '; - } - *p = 0; - return sout; -} -#endif - - namespace xod { -//---------------------------------------------------------------------------- -// Type definitions -//---------------------------------------------------------------------------- -#if __SIZEOF_FLOAT__ == 4 -typedef float Number; -#else -typedef double Number; -#endif -typedef bool Logic; -typedef unsigned long TimeMs; -typedef uint8_t DirtyFlags; - //---------------------------------------------------------------------------- // Global variables //---------------------------------------------------------------------------- @@ -1065,7 +1177,7 @@ State* getState(Context ctx) { void evaluate(Context ctx) { auto state = getState(ctx); auto num = getValue(ctx); - dtostrf(num, 0, 2, state->str); + formatNumber(num, 2, state->str); emitValue(ctx, XString(&state->view)); } diff --git a/workspace/two-button-switch/__fixtures__/arduino.cpp b/workspace/two-button-switch/__fixtures__/arduino.cpp index 7b094051..e0edab8f 100644 --- a/workspace/two-button-switch/__fixtures__/arduino.cpp +++ b/workspace/two-button-switch/__fixtures__/arduino.cpp @@ -56,6 +56,24 @@ typename remove_reference::type&& move(T&& a) { } // namespace std } // namespace xod +/*============================================================================= + * + * + * Basic XOD types + * + * + =============================================================================*/ +namespace xod { +#if __SIZEOF_FLOAT__ == 4 +typedef float Number; +#else +typedef double Number; +#endif +typedef bool Logic; +typedef unsigned long TimeMs; +typedef uint8_t DirtyFlags; +} // namespace xod + /*============================================================================= * * @@ -573,10 +591,176 @@ template bool equal(List lhs, List rhs) { return !lhsIt && !rhsIt; } +template bool operator == (List lhs, List rhs) { + return equal(lhs, rhs); +} + } // namespace xod #endif +/*============================================================================= + * + * + * Format Numbers + * + * + =============================================================================*/ + +/** + * Provide `formatNumber` cross-platform number to string converter function. + * + * Taken from here: + * https://github.com/client9/stringencoders/blob/master/src/modp_numtoa.c + * Original function name: `modp_dtoa2`. + * + * Modified: + * - `isnan` instead of tricky comparing and return "NaN" + * - handle Infinity values and return "Inf" or "-Inf" + * - return `OVF` and `-OVF` for numbers bigger than max possible, instead of using `sprintf` + * - use `Number` instead of double + * - if negative number rounds to zero, return just "0" instead of "-0" + * + * This is a replacement of `dtostrf`. + */ + +#ifndef XOD_FORMAT_NUMBER_H +#define XOD_FORMAT_NUMBER_H + +namespace xod { + +/** + * Powers of 10 + * 10^0 to 10^9 + */ +static const Number powers_of_10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, + 10000000, 100000000, 1000000000 }; + +static void strreverse(char* begin, char* end) { + char aux; + while (end > begin) + aux = *end, *end-- = *begin, *begin++ = aux; +}; + +size_t formatNumber(Number value, int prec, char* str) { + if (isnan(value)) { + strcpy(str, "NaN"); + return (size_t)3; + } + + if (isinf(value)) { + bool isNegative = value < 0; + strcpy(str, isNegative ? "-Inf" : "Inf"); + return (size_t)isNegative ? 4 : 3; + } + + /* if input is larger than thres_max return "OVF" */ + const Number thres_max = (Number)(0x7FFFFFFF); + + Number diff = 0.0; + char* wstr = str; + + if (prec < 0) { + prec = 0; + } else if (prec > 9) { + /* precision of >= 10 can lead to overflow errors */ + prec = 9; + } + + /* we'll work in positive values and deal with the + negative sign issue later */ + int neg = 0; + if (value < 0) { + neg = 1; + value = -value; + } + + int whole = (int)value; + Number tmp = (value - whole) * powers_of_10[prec]; + uint32_t frac = (uint32_t)(tmp); + diff = tmp - frac; + + if (diff > 0.5) { + ++frac; + /* handle rollover, e.g. case 0.99 with prec 1 is 1.0 */ + if (frac >= powers_of_10[prec]) { + frac = 0; + ++whole; + } + } else if (diff == 0.5 && prec > 0 && (frac & 1)) { + /* if halfway, round up if odd, OR + if last digit is 0. That last part is strange */ + ++frac; + if (frac >= powers_of_10[prec]) { + frac = 0; + ++whole; + } + } else if (diff == 0.5 && prec == 0 && (whole & 1)) { + ++frac; + if (frac >= powers_of_10[prec]) { + frac = 0; + ++whole; + } + } + + if (value > thres_max) { + if (neg) { + strcpy(str, "-OVF"); + return (size_t)4; + } + strcpy(str, "OVF"); + return (size_t)3; + } + + int has_decimal = 0; + int count = prec; + bool notzero = frac > 0; + + /* Remove ending zeros */ + if (prec > 0) { + while (count > 0 && ((frac % 10) == 0)) { + count--; + frac /= 10; + } + } + + while (count > 0) { + --count; + *wstr++ = (char)(48 + (frac % 10)); + frac /= 10; + has_decimal = 1; + } + + if (frac > 0) { + ++whole; + } + + /* add decimal */ + if (has_decimal) { + *wstr++ = '.'; + } + + notzero = notzero || whole > 0; + + /* do whole part + * Take care of sign conversion + * Number is reversed. + */ + do + *wstr++ = (char)(48 + (whole % 10)); + while (whole /= 10); + + if (neg && notzero) { + *wstr++ = '-'; + } + *wstr = '\0'; + strreverse(str, wstr - 1); + return (size_t)(wstr - str); +} + +} // namespace xod +#endif + /*============================================================================= * @@ -621,79 +805,7 @@ template bool equal(List lhs, List rhs) { # define pgm_read_ptr(addr) (*(const void **)(addr)) #endif -//---------------------------------------------------------------------------- -// Compatibilities -//---------------------------------------------------------------------------- - -#if !defined(ARDUINO_ARCH_AVR) && !defined(__DTOSTRF_H_) -/* - * Provide dtostrf function for non-AVR platforms. Although many platforms - * provide a stub many others do not. And the stub is based on `sprintf` - * which doesn’t work with floating point formatters on some platforms - * (e.g. Arduino M0). - * - * This is an implementation based on `fcvt` standard function. Taken here: - * https://forum.arduino.cc/index.php?topic=368720.msg2542614#msg2542614 - */ -char *dtostrf(double val, int width, unsigned int prec, char *sout) { - int decpt, sign, reqd, pad; - const char *s, *e; - char *p; - s = fcvt(val, prec, &decpt, &sign); - if (prec == 0 && decpt == 0) { - s = (*s < '5') ? "0" : "1"; - reqd = 1; - } else { - reqd = strlen(s); - if (reqd > decpt) reqd++; - if (decpt == 0) reqd++; - } - if (sign) reqd++; - p = sout; - e = p + reqd; - pad = width - reqd; - if (pad > 0) { - e += pad; - while (pad-- > 0) *p++ = ' '; - } - if (sign) *p++ = '-'; - if (decpt <= 0 && prec > 0) { - *p++ = '0'; - *p++ = '.'; - e++; - while ( decpt < 0 ) { - decpt++; - *p++ = '0'; - } - } - while (p < e) { - *p++ = *s++; - if (p == e) break; - if (--decpt == 0) *p++ = '.'; - } - if (width < 0) { - pad = (reqd + width) * -1; - while (pad-- > 0) *p++ = ' '; - } - *p = 0; - return sout; -} -#endif - - namespace xod { -//---------------------------------------------------------------------------- -// Type definitions -//---------------------------------------------------------------------------- -#if __SIZEOF_FLOAT__ == 4 -typedef float Number; -#else -typedef double Number; -#endif -typedef bool Logic; -typedef unsigned long TimeMs; -typedef uint8_t DirtyFlags; - //---------------------------------------------------------------------------- // Global variables //----------------------------------------------------------------------------