From dfd45651dcdbdc00a72d3e574940318024f0a1c3 Mon Sep 17 00:00:00 2001 From: Ted John Date: Mon, 24 Feb 2020 19:25:18 +0000 Subject: [PATCH] Improve stringify of expressions --- src/openrct2/scripting/ScConsole.hpp | 58 +-------- src/openrct2/scripting/ScriptEngine.cpp | 129 +++++++++++++++++++-- src/openrct2/scripting/ScriptEngine.h | 1 + src/thirdparty/dukglue/register_property.h | 1 + 4 files changed, 122 insertions(+), 67 deletions(-) diff --git a/src/openrct2/scripting/ScConsole.hpp b/src/openrct2/scripting/ScConsole.hpp index c3ccacbf65..4f341ca868 100644 --- a/src/openrct2/scripting/ScConsole.hpp +++ b/src/openrct2/scripting/ScConsole.hpp @@ -13,6 +13,7 @@ # include "../interface/InteractiveConsole.h" # include "Duktape.hpp" +# include "ScriptEngine.h" namespace OpenRCT2::Scripting { @@ -34,49 +35,7 @@ namespace OpenRCT2::Scripting void log(DukValue val) { - std::string str; - switch (val.type()) - { - case DukValue::Type::UNDEFINED: - str = "undefined"; - break; - case DukValue::Type::NULLREF: - str = "null"; - break; - case DukValue::Type::BOOLEAN: - str = val.as_bool() ? "true" : "false"; - break; - case DukValue::Type::NUMBER: - { - const auto d = val.as_double(); - const duk_int_t i = val.as_int(); - if (AlmostEqual(d, i)) - { - str = std::to_string(i); - } - else - { - str = std::to_string(d); - } - break; - } - case DukValue::Type::STRING: - str = val.as_string(); - break; - case DukValue::Type::OBJECT: - str = "{}"; - break; - case DukValue::Type::BUFFER: - str = "buffer"; - break; - case DukValue::Type::POINTER: - str = "pointer"; - break; - case DukValue::Type::LIGHTFUNC: - break; - } - - _console.WriteLine(str); + _console.WriteLine(Stringify(val)); } static void Register(duk_context* ctx) @@ -84,19 +43,6 @@ namespace OpenRCT2::Scripting dukglue_register_method(ctx, &ScConsole::clear, "clear"); dukglue_register_method(ctx, &ScConsole::log, "log"); } - - private: - // Taken from http://en.cppreference.com/w/cpp/types/numeric_limits/epsilon - template - static typename std::enable_if::is_integer, bool>::type AlmostEqual(T x, T y, int32_t ulp = 20) - { - // the machine epsilon has to be scaled to the magnitude of the values used - // and multiplied by the desired precision in ULPs (units in the last place) - return std::abs(x - y) <= std::numeric_limits::epsilon() * std::abs(x + y) * ulp - // unless the result is subnormal - || std::abs(x - y) - < (std::numeric_limits::min)(); // TODO: Remove parentheses around min once the macro is removed - } }; } // namespace OpenRCT2::Scripting diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index ae6273bd78..5f6cce1860 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -35,8 +35,6 @@ using namespace OpenRCT2; using namespace OpenRCT2::Scripting; -static std::string Stringify(duk_context* ctx, duk_idx_t idx); - static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 1; DukContext::DukContext() @@ -335,7 +333,7 @@ void ScriptEngine::ProcessREPL() } else if (duk_get_type(_context, -1) != DUK_TYPE_UNDEFINED) { - std::string result = Stringify(_context, -1); + auto result = Stringify(DukValue::copy_from_stack(_context, -1)); _console.WriteLine(result); } duk_pop(_context); @@ -365,17 +363,126 @@ void ScriptEngine::AddNetworkPlugin(const std::string_view& code) LoadPlugin(plugin); } -static std::string Stringify(duk_context* ctx, duk_idx_t idx) +// Taken from http://en.cppreference.com/w/cpp/types/numeric_limits/epsilon +template +static typename std::enable_if::is_integer, bool>::type AlmostEqual(T x, T y, int32_t ulp = 20) { - auto type = duk_get_type(ctx, idx); - if (type == DUK_TYPE_OBJECT && !duk_is_function(ctx, idx)) + // the machine epsilon has to be scaled to the magnitude of the values used + // and multiplied by the desired precision in ULPs (units in the last place) + return std::abs(x - y) <= std::numeric_limits::epsilon() * std::abs(x + y) * ulp + // unless the result is subnormal + || std::abs(x - y) < (std::numeric_limits::min)(); // TODO: Remove parentheses around min once the macro is removed +} + +std::string OpenRCT2::Scripting::Stringify(const DukValue& val, int32_t depth) +{ + if (depth >= 3) + return "..."; + + std::string str; + switch (val.type()) { - return duk_json_encode(ctx, idx); - } - else - { - return duk_safe_to_string(ctx, idx); + case DukValue::Type::UNDEFINED: + str = "undefined"; + break; + case DukValue::Type::NULLREF: + str = "null"; + break; + case DukValue::Type::BOOLEAN: + str = val.as_bool() ? "true" : "false"; + break; + case DukValue::Type::NUMBER: + { + const auto d = val.as_double(); + const duk_int_t i = val.as_int(); + if (AlmostEqual(d, i)) + { + str = std::to_string(i); + } + else + { + str = std::to_string(d); + } + break; + } + case DukValue::Type::STRING: + str = "\"" + val.as_string() + "\""; + break; + case DukValue::Type::OBJECT: + if (val.is_function()) + { + auto ctx = val.context(); + val.push(); + if (duk_is_c_function(ctx, -1)) + { + str = u8"ƒ [native]"; + } + else if (duk_is_ecmascript_function(ctx, -1)) + { + str = u8"ƒ [ecmascript]"; + } + else + { + str = u8"ƒ [javascript]"; + } + duk_pop(ctx); + } + else if (val.is_array()) + { + str = "["; + auto ctx = val.context(); + val.push(); + auto arrayLen = duk_get_length(ctx, -1); + for (duk_uarridx_t i = 0; i < arrayLen; i++) + { + if (i != 0) + str += ", "; + if (duk_get_prop_index(ctx, -1, i)) + { + auto arrayVal = DukValue::take_from_stack(ctx); + str += Stringify(arrayVal, depth + 1); + } + if (i >= 4) + { + str += ", ..."; + break; + } + } + duk_pop(ctx); + str += "]"; + } + else + { + str = "{"; + auto ctx = val.context(); + val.push(); + duk_enum(ctx, -1, 0); + auto index = 0; + while (duk_next(ctx, -1, 1)) + { + if (index != 0) + str += ", "; + auto value = DukValue::take_from_stack(ctx, -1); + auto key = DukValue::take_from_stack(ctx, -1); + str += Stringify(key, depth + 1); + str += ": "; + str += Stringify(value, depth + 1); + index++; + } + duk_pop_2(ctx); + str += "}"; + } + break; + case DukValue::Type::BUFFER: + str = "buffer"; + break; + case DukValue::Type::POINTER: + str = "pointer"; + break; + case DukValue::Type::LIGHTFUNC: + break; } + return str; } bool OpenRCT2::Scripting::IsGameStateMutable() diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h index 6ebce0af41..3c2a2db8f0 100644 --- a/src/openrct2/scripting/ScriptEngine.h +++ b/src/openrct2/scripting/ScriptEngine.h @@ -168,6 +168,7 @@ namespace OpenRCT2::Scripting bool IsGameStateMutable(); void ThrowIfGameStateNotMutable(); + std::string Stringify(const DukValue& value, int32_t depth = 0); } // namespace OpenRCT2::Scripting diff --git a/src/thirdparty/dukglue/register_property.h b/src/thirdparty/dukglue/register_property.h index c4fd8b36b7..c71dc0a3ac 100644 --- a/src/thirdparty/dukglue/register_property.h +++ b/src/thirdparty/dukglue/register_property.h @@ -115,6 +115,7 @@ void dukglue_register_property(duk_context* ctx, duk_uint_t flags = DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER + | DUK_DEFPROP_SET_ENUMERABLE | DUK_DEFPROP_HAVE_CONFIGURABLE /* set not configurable (from JS) */ | DUK_DEFPROP_FORCE /* allow overriding built-ins and previously defined properties */;