Javascript.NodeJS icon indicating copy to clipboard operation
Javascript.NodeJS copied to clipboard

Undefined type

Open Taritsyn opened this issue 4 years ago • 7 comments

Hello, Jeremy!

In Jering.Javascript.NodeJS values of null and undefined types are always returned as null, that causes inconvenience. Most JS engines for .NET has a clearly separation for null and undefined types. As a rule, to represent undefined in .NET is used a custom type (see as examples the ClearScript and Jurassic).

Taritsyn avatar Nov 26 '19 18:11 Taritsyn

Hey! Interesting point. I've just taken a look at Jurassic's API, in particular ScriptEngine:

/// <summary>
/// Executes the given source code.  Execution is bound to the global scope.
/// </summary>
/// <param name="code"> The javascript source code to execute. </param>
/// <returns> The result of executing the source code. </returns>
/// <exception cref="ArgumentNullException"> <paramref name="code"/> is a <c>null</c> reference. </exception>
public object Evaluate(string code)
{
    return Evaluate(new StringScriptSource(code));
}

/// <summary>
/// Executes the given source code.  Execution is bound to the global scope.
/// </summary>
/// <typeparam name="T"> The type to convert the result to. </typeparam>
/// <param name="code"> The javascript source code to execute. </param>
/// <returns> The result of executing the source code. </returns>
/// <exception cref="ArgumentNullException"> <paramref name="code"/> is a <c>null</c> reference. </exception>
public T Evaluate<T>(string code)
{
    return TypeConverter.ConvertTo<T>(this, Evaluate(code));
}

To support returning an instance of a custom type Undefined, we'd need INodeJSService.InvokeFrom* methods without type parameters.

For example, at present, T InvokeFromString<T> can only return an instance of type T or default(T). So we'd need object InvokeFromString which under the hood would return ExpandoObject/null/Undefined.

Would that be enough? Anything I missed out?

JeremyTCD avatar Nov 27 '19 04:11 JeremyTCD

For example, at present, T InvokeFromString<T> can only return an instance of type T or default(T).

For this case, you can implement the Undefined type as a structure. Main thing is that the host has a type responsible for working with undefined.

Taritsyn avatar Nov 27 '19 06:11 Taritsyn

As an example of how I work with a Undefined type, you can see the source code of V8JsEngine class.

Taritsyn avatar Nov 27 '19 06:11 Taritsyn

Thanks for the extra info

Invoke Methods with Generic Parameters

For this case, you can implement the Undefined type as a structure.

I'm not sure if I understood you right on this point.

I've looked through V8JsEngine, T InnerEvaluate<T> calls object InnerEvaluate, then converts the result to an instance of type T:

protected override T InnerEvaluate<T>(string expression, string documentName)
{
    object result = InnerEvaluate(expression, documentName);

    return TypeConverter.ConvertToType<T>(result);
}

It seems to me that T InnerEvaluate<T> can only return T or default(T), even if Undefined is a struct.

Plan

Will this plan meet your needs?

  1. Create an Undefined custom type, like Clearscripts:
public class Undefined
{
    internal static readonly Undefined Value = new Undefined();

    private Undefined()
    {
    }

    #region Object overrides

    /// <summary>
    /// Returns a string that represents the current object.
    /// </summary>
    /// <returns>A string that represents the current object.</returns>
    /// <remarks>
    /// The <see cref="Undefined"/> version of this method returns "[undefined]".
    /// </remarks>
    public override string ToString()
    {
        return "[undefined]";
    }

    #endregion
}
  1. Add object InvokeFrom* methods to INodeJSService. Under the hood they return ExpandoObject/null/Undefined. Similar to Clearscript's evaluate methods - https://microsoft.github.io/ClearScript/Reference/html/M_Microsoft_ClearScript_ScriptEngine_Evaluate_1.htm.

Also, within the ExpandoObject, any field that was undefined in the Javascript result must be a reference to an instance of the Undefined type.

JeremyTCD avatar Nov 27 '19 07:11 JeremyTCD

I've looked through V8JsEngine, T InnerEvaluate<T> calls object InnerEvaluate, then converts the result to an instance of type T:

Microsoft ClearScript.V8 always returns a value of object type, therefore, in this case, the InnerEvaluate<T> method is only responsible for casting to the specified type. In this case, you need to look at the object InnerEvaluate(string expression, string documentName) method.

  1. Create an Undefined custom type, like Clearscripts:

The implementation details of this type are not particularly important to me. If a structure is more suitable for you, then implement this type as a structure.

Only two things are important to me:

  1. I need to understand that the return value is undefined.
  2. To be able to create a undefined value on the host and pass it to your library.

I.e. that there was an opportunity to create the MapToScriptType and MapToHostType methods.

  1. Add object InvokeFrom* methods to INodeJSService.

Non-generic versions of the InvokeFrom* methods would be very helpful.

Taritsyn avatar Nov 27 '19 11:11 Taritsyn

Okay, I believe we're on the same page. This will be a useful addition for those who want to return dynamic objects. Will tag you when I create the PR!

JeremyTCD avatar Nov 27 '19 11:11 JeremyTCD

OK.

Taritsyn avatar Nov 27 '19 11:11 Taritsyn