Skip to content

Commit

Permalink
[FOLD]
Browse files Browse the repository at this point in the history
  • Loading branch information
alandefreitas committed Oct 21, 2023
1 parent f4b335d commit 43540ed
Show file tree
Hide file tree
Showing 3 changed files with 274 additions and 13 deletions.
125 changes: 125 additions & 0 deletions include/mrdox/Support/JavaScript.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,23 @@ class Scope
Expected<void>
script(std::string_view jsCode);

/** Compile and run a expression.
This function compiles and executes
the specified JavaScript code. The script
can be used to execute commands or define
global variables in the parent context.
It evaluates the ECMAScript source code and
converts any internal errors to @ref Error.
@param jsCode The JavaScript code to execute.
*/
MRDOX_DECL
Expected<Value>
eval(std::string_view jsCode);

/** Compile a script and push results to stack.
Compile ECMAScript source code and return it
Expand Down Expand Up @@ -273,6 +290,28 @@ class Scope
//------------------------------------------------

/** An ECMAScript value.
This class represents a value in the
JavaScript interpreter.
A value is a variable that is defined
in a @ref Scope. It can be a primitive
type or an object.
A @ref Value not associated with a
@ref Scope is undefined.
The user is responsible for ensuring that
the lifetime of a @ref Value does not
exceed the lifetime of the @ref Scope
that created it.
A value can be converted to a DOM value
using the @ref getDom function.
@see Scope
@see Type
*/
class Value
{
Expand All @@ -285,22 +324,103 @@ class Value
Value(int, Scope&) noexcept;

public:
/** Destructor
If the value is associated with a
@ref Scope and it is on top of the
stack, it is popped. Also, if
there are no other Value references
to the @ref Scope, all variables
defined in that scope are popped
via @ref Scope::reset.
*/
MRDOX_DECL ~Value();

/** Constructor
Construct a value that is not associated
with a @ref Scope.
The value is undefined.
*/
MRDOX_DECL Value() noexcept;

/** Constructor
The function pushes a duplicate of
value to the stack and associates
the new value the top of the stack.
*/
MRDOX_DECL Value(Value const&);

/** Constructor
The function associates the
existing value with this object.
*/
MRDOX_DECL Value(Value&&) noexcept;

/** Copy assignment.
@copydetails Value(Value const&)
*/
MRDOX_DECL Value& operator=(Value const&);

/** Move assignment.
@copydetails Value(Value&&)
*/
MRDOX_DECL Value& operator=(Value&&) noexcept;

/** Return the type of the value.
This function returns the JavaScript
type of the value.
The type can represent a primitive
type (such as boolean, number,
and string) or an object.
When the type is an object, the
return type also classifies the
object as an array or function.
An array is an object with the
internal ECMAScript class `Array`
or a Proxy wrapping an `Array`.
A function is an object with the
internal ECMAScript class `Function`.
*/
MRDOX_DECL Type type() const noexcept;

/// Check if the value is undefined.
bool isUndefined() const noexcept;

/// Check if the value is null.
bool isNull() const noexcept;

/// Check if the value is a boolean.
bool isBoolean() const noexcept;

/// Check if the value is a number.
bool isNumber() const noexcept;

/// Check if the value is a string.
bool isString() const noexcept;

/// Check if the value is an array.
bool isArray() const noexcept;

/// Check if the value is an object.
bool isObject() const noexcept;

/// Check if the value is a function.
bool isFunction() const noexcept;

std::string getString() const;
Expand Down Expand Up @@ -408,6 +528,11 @@ inline bool Value::isObject() const noexcept
return type() == Type::object;
}

inline bool Value::isArray() const noexcept
{
return type() == Type::array;
}

inline bool Value::isFunction() const noexcept
{
return type() == Type::function;
Expand Down
27 changes: 16 additions & 11 deletions src/lib/Support/JavaScript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,21 @@ script(
return {};
}

Expected<Value>
Scope::
eval(
std::string_view jsCode)
{
Access A(*this);
duk_int_t failed = duk_peval_lstring(
A, jsCode.data(), jsCode.size());
if (failed)
{
return Unexpected(dukM_popError(*this));
}
return Access::construct<Value>(duk_get_top_index(A), *this);
}

Expected<Value>
Scope::
compile_script(
Expand Down Expand Up @@ -894,7 +909,7 @@ Value::
Access A(*scope_);
if(idx_ == duk_get_top(A) - 1)
duk_pop(A);
A.release(*scope_);
Access::release(*scope_);
}

// construct an empty value
Expand Down Expand Up @@ -979,16 +994,6 @@ type() const noexcept
}
}

bool
Value::
isArray() const noexcept
{
Access A(*scope_);
return
isObject() &&
duk_is_array(A, idx_);
}

std::string
Value::
getString() const
Expand Down
135 changes: 133 additions & 2 deletions src/test/Support/Javascript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,27 @@ struct Path_test
// script
{
Scope scope(ctx);
scope.script("var x = 1;");
scope.script("print(x);");
Expected<void> r = scope.script("var x = 1;");
BOOST_TEST(r);
r = scope.script("print(x);");
BOOST_TEST(!r);
auto exp = scope.getGlobal("x");
BOOST_TEST(exp);
js::Value x = *exp;
BOOST_TEST(x.isNumber());
BOOST_TEST(x.getDom() == 1);
}

// eval
{
Scope scope(ctx);
Expected<Value> r = scope.eval("1 + 2 + 3");
BOOST_TEST(r);
Value v = r.value();
BOOST_TEST(v.isNumber());
BOOST_TEST(v.getDom() == 6);
}

// compile_script
{
// last expression as implicit return value
Expand Down Expand Up @@ -166,7 +178,126 @@ struct Path_test
void
test_value()
{
// Value()
{
Context ctx;
Scope scope(ctx);
Value v;
BOOST_TEST(v.isUndefined());
}

// Value(Value const&)
{
Context ctx;
Scope scope(ctx);
Value v1;
Value v2(v1);
BOOST_TEST(v2.isUndefined());
}

// Value(Value&&)
{
Context ctx;
Scope scope(ctx);
Value v1;
Value v2(std::move(v1));
BOOST_TEST(v2.isUndefined());
}

// operator=(Value const&)
{
Context ctx;
Scope scope(ctx);
Value v1;
Value v2;
v2 = v1;
BOOST_TEST(v2.isUndefined());
}

// operator=(Value&&)
{
Context ctx;
Scope scope(ctx);
Value v1;
Value v2;
v2 = std::move(v1);
BOOST_TEST(v2.isUndefined());
}

// type()
{
// undefined
{
Context context;
Scope scope(context);
Value x = scope.eval("undefined").value();
BOOST_TEST(x.isUndefined());
BOOST_TEST(x.type() == Type::undefined);
}

// null
{
Context context;
Scope scope(context);
Value x = scope.eval("null").value();
BOOST_TEST(x.isNull());
BOOST_TEST(x.type() == Type::null);
}

// boolean
{
Context context;
Scope scope(context);
Value x = scope.eval("true").value();
BOOST_TEST(x.isBoolean());
BOOST_TEST(x.type() == Type::boolean);
}

// number
{
Context context;
Scope scope(context);
Value x = scope.eval("1 + 2 + 3").value();
BOOST_TEST(x.isNumber());
BOOST_TEST(x.type() == Type::number);
}

// string
{
Context context;
Scope scope(context);
Value x = scope.eval("'hello world'").value();
BOOST_TEST(x.isString());
BOOST_TEST(x.type() == Type::string);
}

// object
{
Context context;
Scope scope(context);
Value x = scope.eval("({ x: 1 })").value();
BOOST_TEST(x.isObject());
BOOST_TEST(x.type() == Type::object);
}

// function
{
Context context;
Scope scope(context);
Value x = scope.eval("(function() { return 1; })").value();
BOOST_TEST(x.isFunction());
BOOST_TEST(x.type() == Type::function);
}

// array
{
Context context;
Scope scope(context);
Value x = scope.eval("([1, 2, 3])").value();
BOOST_TEST(x.isArray());
BOOST_TEST(x.type() == Type::array);
}
}
}

void run()
Expand Down

0 comments on commit 43540ed

Please sign in to comment.