diff --git a/doc/autogen/types/bytes.rst b/doc/autogen/types/bytes.rst index 6c10d218e6..5da19e1dbc 100644 --- a/doc/autogen/types/bytes.rst +++ b/doc/autogen/types/bytes.rst @@ -96,6 +96,13 @@ Interprets the ``bytes`` as representing an binary number encoded with the given byte order, and converts it into signed integer. +.. spicy:method:: bytes::to_real bytes to_real False real () + + Interprets the ``bytes`` as representing an ASCII-encoded floating + point number and converts that into a ``real``. The data can be in + either decimal or hexadecimal format. If it cannot be parsed as + either, throws an `InvalidValue` exception. + .. spicy:method:: bytes::to_time bytes to_time False time ([ base: uint<64> ]) Interprets the ``bytes`` as representing a number of seconds since the diff --git a/hilti/runtime/include/types/bytes.h b/hilti/runtime/include/types/bytes.h index 06c50b406f..4541eef1b5 100644 --- a/hilti/runtime/include/types/bytes.h +++ b/hilti/runtime/include/types/bytes.h @@ -488,6 +488,14 @@ class Bytes : protected std::string { */ uint64_t toUInt(hilti::rt::ByteOrder byte_order) const; + /** + * Interprets the data as an ASCII representation of a floating point value + * and extracts that. The data must be in a format that `strtod` can handle. + * + * @return converted real value + */ + double toReal() const; + /** * Interprets the data as an ASCII representation of a integer value * representing seconds since the UNIX epoch, and extracts that. diff --git a/hilti/runtime/src/types/bytes.cc b/hilti/runtime/src/types/bytes.cc index 2bec9fb308..26f712031b 100644 --- a/hilti/runtime/src/types/bytes.cc +++ b/hilti/runtime/src/types/bytes.cc @@ -205,6 +205,24 @@ uint64_t Bytes::toUInt(ByteOrder byte_order) const { return i; } +double Bytes::toReal() const { + // Ensure there are no null bytes inside our data, so that we can call strtod(). + if ( Base::find('\0') != Base::npos ) + throw InvalidValue("cannot parse real value: null byte in data"); + + const char* cstr = Base::c_str(); + char* endp = nullptr; + + errno = 0; + auto d = strtod(cstr, &endp); + if ( endp == cstr || *endp != '\0' || (d == HUGE_VAL && errno == ERANGE) ) { + errno = 0; + throw InvalidValue(fmt("cannot parse real value: %s", cstr)); + } + + return d; +} + Result Bytes::match(const RegExp& re, unsigned int group) const { auto groups = re.matchGroups(*this); diff --git a/hilti/toolchain/include/ast/forward.h b/hilti/toolchain/include/ast/forward.h index 2b7b171b62..584280eb98 100644 --- a/hilti/toolchain/include/ast/forward.h +++ b/hilti/toolchain/include/ast/forward.h @@ -192,6 +192,7 @@ class ToIntAscii; class ToUIntAscii; class ToIntBinary; class ToUIntBinary; +class ToRealAscii; class ToTimeAscii; class ToTimeBinary; class Decode; diff --git a/hilti/toolchain/include/ast/node-tag.h b/hilti/toolchain/include/ast/node-tag.h index cb70a06387..d9f95c20fc 100644 --- a/hilti/toolchain/include/ast/node-tag.h +++ b/hilti/toolchain/include/ast/node-tag.h @@ -193,12 +193,13 @@ constexpr Tag SumAssignStreamView = 922; constexpr Tag SumAssignUInt8 = 923; constexpr Tag ToIntAscii = 924; constexpr Tag ToIntBinary = 925; -constexpr Tag ToTimeAscii = 926; -constexpr Tag ToTimeBinary = 927; -constexpr Tag ToUIntAscii = 928; -constexpr Tag ToUIntBinary = 929; -constexpr Tag Unequal = 930; -constexpr Tag UpperCase = 931; +constexpr Tag ToRealAscii = 926; +constexpr Tag ToTimeAscii = 927; +constexpr Tag ToTimeBinary = 928; +constexpr Tag ToUIntAscii = 929; +constexpr Tag ToUIntBinary = 930; +constexpr Tag Unequal = 931; +constexpr Tag UpperCase = 932; namespace iterator { constexpr Tag Deref = 1000; diff --git a/hilti/toolchain/include/ast/operators/bytes.h b/hilti/toolchain/include/ast/operators/bytes.h index 8b316e10a2..5a87b2c452 100644 --- a/hilti/toolchain/include/ast/operators/bytes.h +++ b/hilti/toolchain/include/ast/operators/bytes.h @@ -49,6 +49,7 @@ HILTI_NODE_OPERATOR(bytes, ToIntAscii) HILTI_NODE_OPERATOR(bytes, ToUIntAscii) HILTI_NODE_OPERATOR(bytes, ToIntBinary) HILTI_NODE_OPERATOR(bytes, ToUIntBinary) +HILTI_NODE_OPERATOR(bytes, ToRealAscii) HILTI_NODE_OPERATOR(bytes, ToTimeAscii) HILTI_NODE_OPERATOR(bytes, ToTimeBinary) HILTI_NODE_OPERATOR(bytes, Decode) diff --git a/hilti/toolchain/include/ast/visitor-dispatcher.h b/hilti/toolchain/include/ast/visitor-dispatcher.h index c2d738e11e..cfac0a37cb 100644 --- a/hilti/toolchain/include/ast/visitor-dispatcher.h +++ b/hilti/toolchain/include/ast/visitor-dispatcher.h @@ -161,6 +161,7 @@ class Dispatcher { virtual void operator()(hilti::operator_::bytes::ToUIntAscii* n) {} virtual void operator()(hilti::operator_::bytes::ToIntBinary* n) {} virtual void operator()(hilti::operator_::bytes::ToUIntBinary* n) {} + virtual void operator()(hilti::operator_::bytes::ToRealAscii* n) {} virtual void operator()(hilti::operator_::bytes::ToTimeAscii* n) {} virtual void operator()(hilti::operator_::bytes::ToTimeBinary* n) {} virtual void operator()(hilti::operator_::bytes::Decode* n) {} diff --git a/hilti/toolchain/src/ast/operators/bytes.cc b/hilti/toolchain/src/ast/operators/bytes.cc index a70078c74f..731e9c75ad 100644 --- a/hilti/toolchain/src/ast/operators/bytes.cc +++ b/hilti/toolchain/src/ast/operators/bytes.cc @@ -876,6 +876,29 @@ byte order, and converts it into an unsigned integer. }; HILTI_OPERATOR_IMPLEMENTATION(ToUIntBinary); +class ToRealAscii : public BuiltInMemberCall { +public: + Signature signature(Builder* builder) const final { + return Signature{ + .kind = Kind::MemberCall, + .self = {parameter::Kind::In, builder->typeBytes()}, + .member = "to_real", + .result = {Constness::Const, builder->typeReal()}, + .ns = "bytes", + .doc = + R"( +Interprets the ``bytes`` as representing an ASCII-encoded floating point number +and converts that into a ``real``. The data can be in either decimal or +hexadecimal format. If it cannot be parsed as either, throws an `InvalidValue` +exception. +)", + }; + } + + HILTI_OPERATOR(hilti, bytes::ToRealAscii); +}; +HILTI_OPERATOR_IMPLEMENTATION(ToRealAscii); + class ToTimeAscii : public BuiltInMemberCall { public: Signature signature(Builder* builder) const final { diff --git a/hilti/toolchain/src/compiler/codegen/operators.cc b/hilti/toolchain/src/compiler/codegen/operators.cc index 37701606f9..ebe7828f3f 100644 --- a/hilti/toolchain/src/compiler/codegen/operators.cc +++ b/hilti/toolchain/src/compiler/codegen/operators.cc @@ -247,6 +247,11 @@ struct Visitor : hilti::visitor::PreOrder { result = fmt("%s.toUInt(%s)", self, optionalArgument(args, 0)); } + void operator()(operator_::bytes::ToRealAscii* n) final { + auto [self, args] = methodArguments(n); + result = fmt("%s.toReal()", self); + } + void operator()(operator_::bytes::ToTimeAscii* n) final { auto [self, args] = methodArguments(n); result = fmt("%s.toTime(%s)", self, optionalArgument(args, 0)); diff --git a/tests/Baseline/hilti.types.bytes.to-real/output b/tests/Baseline/hilti.types.bytes.to-real/output new file mode 100644 index 0000000000..49d861c74c --- /dev/null +++ b/tests/Baseline/hilti.types.bytes.to-real/output @@ -0,0 +1 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. diff --git a/tests/hilti/types/bytes/to-real.hlt b/tests/hilti/types/bytes/to-real.hlt new file mode 100644 index 0000000000..f24160d8da --- /dev/null +++ b/tests/hilti/types/bytes/to-real.hlt @@ -0,0 +1,14 @@ +# @TEST-EXEC: hiltic -j %INPUT >output +# @TEST-EXEC: btest-diff output + +module Test { + +assert(b"3.14".to_real() == 3.14); +assert(b"314e5".to_real() == 314e5); +assert(b"0X1.BC70A3D70A3D7P+6".to_real() == 111.11); + +assert-exception(b"3.14XYZ".to_real()); +assert-exception(b"XXX".to_real()); +assert-exception(b"\03.14".to_real()); + +}