Improve stringify of expressions

This commit is contained in:
Ted John 2020-02-24 19:25:18 +00:00
parent ae0c2638e3
commit dfd45651dc
4 changed files with 122 additions and 67 deletions

View File

@ -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<double>(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<class T>
static typename std::enable_if<!std::numeric_limits<T>::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<T>::epsilon() * std::abs(x + y) * ulp
// unless the result is subnormal
|| std::abs(x - y)
< (std::numeric_limits<T>::min)(); // TODO: Remove parentheses around min once the macro is removed
}
};
} // namespace OpenRCT2::Scripting

View File

@ -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<class T>
static typename std::enable_if<!std::numeric_limits<T>::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<T>::epsilon() * std::abs(x + y) * ulp
// unless the result is subnormal
|| std::abs(x - y) < (std::numeric_limits<T>::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<double>(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()

View File

@ -168,6 +168,7 @@ namespace OpenRCT2::Scripting
bool IsGameStateMutable();
void ThrowIfGameStateNotMutable();
std::string Stringify(const DukValue& value, int32_t depth = 0);
} // namespace OpenRCT2::Scripting

View File

@ -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 */;