1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 10:48:11 +00:00

LibJS: Add side-effect-free version of Value::to_string()

There are now two API's on Value:

- Value::to_string(Interpreter&) -- may throw.
- Value::to_string_without_side_effects() -- will never throw.

These are some pretty big sweeping changes, so it's possible that I did
some part the wrong way. We'll work it out as we go. :^)

Fixes #2123.
This commit is contained in:
Andreas Kling 2020-05-15 13:39:24 +02:00
parent d8aa2a6997
commit c6ddbd1f3e
25 changed files with 285 additions and 112 deletions

View file

@ -26,6 +26,7 @@
#include <AK/FlyString.h>
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <LibJS/Heap/Heap.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/Array.h>
@ -60,7 +61,7 @@ Function& Value::as_function()
return static_cast<Function&>(as_object());
}
String Value::to_string() const
String Value::to_string_without_side_effects() const
{
if (is_boolean())
return as_bool() ? "true" : "false";
@ -78,8 +79,47 @@ String Value::to_string() const
if (is_infinity())
return as_double() < 0 ? "-Infinity" : "Infinity";
// FIXME: This needs improvement.
if ((double)to_i32() == as_double())
if (is_integer())
return String::number(to_i32());
return String::format("%.4f", as_double());
}
if (is_string())
return m_value.as_string->string();
ASSERT(is_object());
return String::format("[object %s]", as_object().class_name());
}
PrimitiveString* Value::to_primitive_string(Interpreter & interpreter)
{
if (is_string())
return &as_string();
auto string = to_string(interpreter);
if (interpreter.exception())
return nullptr;
return js_string(interpreter, string);
}
String Value::to_string(Interpreter& interpreter) const
{
if (is_boolean())
return as_bool() ? "true" : "false";
if (is_null())
return "null";
if (is_undefined())
return "undefined";
if (is_number()) {
if (is_nan())
return "NaN";
if (is_infinity())
return as_double() < 0 ? "-Infinity" : "Infinity";
if (is_integer())
return String::number(to_i32());
return String::format("%.4f", as_double());
}
@ -89,13 +129,11 @@ String Value::to_string() const
// FIXME: Maybe we should pass in the Interpreter& and call interpreter.exception() instead?
if (primitive_value.is_empty())
return {};
return primitive_value.to_string();
return primitive_value.to_string(interpreter);
}
if (is_string())
return m_value.as_string->string();
ASSERT_NOT_REACHED();
ASSERT(is_string());
return m_value.as_string->string();
}
bool Value::to_boolean() const
@ -305,10 +343,24 @@ Value unsigned_right_shift(Interpreter&, Value lhs, Value rhs)
Value add(Interpreter& interpreter, Value lhs, Value rhs)
{
auto lhs_primitive = lhs.to_primitive(interpreter);
if (interpreter.exception())
return {};
auto rhs_primitive = rhs.to_primitive(interpreter);
if (interpreter.exception())
return {};
if (lhs_primitive.is_string() || rhs_primitive.is_string())
return js_string(interpreter.heap(), String::format("%s%s", lhs_primitive.to_string().characters(), rhs_primitive.to_string().characters()));
if (lhs_primitive.is_string() || rhs_primitive.is_string()) {
auto lhs_string = lhs_primitive.to_string(interpreter);
if (interpreter.exception())
return {};
auto rhs_string = rhs_primitive.to_string(interpreter);
if (interpreter.exception())
return {};
StringBuilder builder(lhs_string.length() + rhs_string.length());
builder.append(lhs_string);
builder.append(rhs_string);
return js_string(interpreter, builder.to_string());
}
return Value(lhs_primitive.to_number().as_double() + rhs_primitive.to_number().as_double());
}
@ -350,7 +402,11 @@ Value in(Interpreter& interpreter, Value lhs, Value rhs)
if (!rhs.is_object())
return interpreter.throw_exception<TypeError>("'in' operator must be used on object");
return Value(!rhs.as_object().get(lhs.to_string()).is_empty());
auto lhs_string = lhs.to_string(interpreter);
if (interpreter.exception())
return {};
return Value(!rhs.as_object().get(lhs_string).is_empty());
}
Value instance_of(Interpreter&, Value lhs, Value rhs)
@ -367,7 +423,7 @@ Value instance_of(Interpreter&, Value lhs, Value rhs)
const LogStream& operator<<(const LogStream& stream, const Value& value)
{
return stream << (value.is_empty() ? "<empty>" : value.to_string());
return stream << (value.is_empty() ? "<empty>" : value.to_string_without_side_effects());
}
bool same_value(Interpreter& interpreter, Value lhs, Value rhs)