Skip to content

Commit

Permalink
Add better debugger view support via debugger attributes (#1656)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma authored Oct 23, 2023
1 parent f12cb7d commit adeb772
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 125 deletions.
144 changes: 68 additions & 76 deletions Jint.Repl/Program.cs
Original file line number Diff line number Diff line change
@@ -1,97 +1,89 @@
using System.Diagnostics;
using System.Reflection;
using Esprima;
using Jint;
using Jint.Native;
using Jint.Native.Json;
using Jint.Runtime;

namespace Jint.Repl
var engine = new Engine(cfg => cfg
.AllowClr()
);

engine
.SetValue("print", new Action<object>(Console.WriteLine))
.SetValue("load", new Func<string, object>(
path => engine.Evaluate(File.ReadAllText(path)))
);

var filename = args.Length > 0 ? args[0] : "";
if (!string.IsNullOrEmpty(filename))
{
internal static class Program
if (!File.Exists(filename))
{
private static void Main(string[] args)
{
var engine = new Engine(cfg => cfg
.AllowClr()
);

engine
.SetValue("print", new Action<object>(Console.WriteLine))
.SetValue("load", new Func<string, object>(
path => engine.Evaluate(File.ReadAllText(path)))
);
Console.WriteLine("Could not find file: {0}", filename);
}

var filename = args.Length > 0 ? args[0] : "";
if (!string.IsNullOrEmpty(filename))
{
if (!File.Exists(filename))
{
Console.WriteLine("Could not find file: {0}", filename);
}
var script = File.ReadAllText(filename);
engine.Evaluate(script, "repl");
return;
}

var script = File.ReadAllText(filename);
engine.Evaluate(script, "repl");
return;
}
var assembly = Assembly.GetExecutingAssembly();
var fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
var version = fvi.FileVersion;

var assembly = Assembly.GetExecutingAssembly();
var fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
var version = fvi.FileVersion;
Console.WriteLine("Welcome to Jint ({0})", version);
Console.WriteLine("Type 'exit' to leave, " +
"'print()' to write on the console, " +
"'load()' to load scripts.");
Console.WriteLine();

Console.WriteLine("Welcome to Jint ({0})", version);
Console.WriteLine("Type 'exit' to leave, " +
"'print()' to write on the console, " +
"'load()' to load scripts.");
Console.WriteLine();
var defaultColor = Console.ForegroundColor;
var parserOptions = new ParserOptions
{
Tolerant = true,
RegExpParseMode = RegExpParseMode.AdaptToInterpreted
};

var defaultColor = Console.ForegroundColor;
var parserOptions = new ParserOptions
{
Tolerant = true,
RegExpParseMode = RegExpParseMode.AdaptToInterpreted
};
var serializer = new JsonSerializer(engine);

var serializer = new JsonSerializer(engine);
while (true)
{
Console.ForegroundColor = defaultColor;
Console.Write("jint> ");
var input = Console.ReadLine();
if (input is "exit" or ".exit")
{
return;
}

while (true)
try
{
var result = engine.Evaluate(input, parserOptions);
JsValue str = result;
if (!result.IsPrimitive() && result is not IPrimitiveInstance)
{
str = serializer.Serialize(result, JsValue.Undefined, " ");
if (str == JsValue.Undefined)
{
Console.ForegroundColor = defaultColor;
Console.Write("jint> ");
var input = Console.ReadLine();
if (input is "exit" or ".exit")
{
return;
}

try
{
var result = engine.Evaluate(input, parserOptions);
JsValue str = result;
if (!result.IsPrimitive() && result is not IPrimitiveInstance)
{
str = serializer.Serialize(result, JsValue.Undefined, " ");
if (str == JsValue.Undefined)
{
str = result;
}
}
else if (result.IsString())
{
str = serializer.Serialize(result, JsValue.Undefined, JsValue.Undefined);
}
Console.WriteLine(str);
}
catch (JavaScriptException je)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(je.ToString());
}
catch (Exception e)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(e.Message);
}
str = result;
}
}
else if (result.IsString())
{
str = serializer.Serialize(result, JsValue.Undefined, JsValue.Undefined);
}
Console.WriteLine(str);
}
catch (JavaScriptException je)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(je.ToString());
}
catch (Exception e)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(e.Message);
}
}
33 changes: 33 additions & 0 deletions Jint.Tests/Runtime/InteropTests.Dynamic.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System.Dynamic;
using Jint.Native;
using Jint.Native.Symbol;
using Jint.Tests.Runtime.Domain;

namespace Jint.Tests.Runtime
{
Expand All @@ -14,6 +17,36 @@ public void CanAccessExpandoObject()
Assert.Equal("test", engine.Evaluate("expando.Name").ToString());
}

[Fact]
public void DebugView()
{
// allows displaying different local variables under debugger

var engine = new Engine();
var boolNet = true;
var boolJint = (JsBoolean) boolNet;
var doubleNet = 12.34;
var doubleJint = (JsNumber) doubleNet;
var integerNet = 42;
var integerJint = (JsNumber) integerNet;
var stringNet = "ABC";
var stringJint = (JsString) stringNet;
var arrayNet = new[] { 1, 2, 3 };
var arrayListNet = new List<int> { 1, 2, 3 };
var arrayJint = new JsArray(engine, arrayNet.Select(x => (JsNumber) x).ToArray());

var objectNet = new Person { Name = "name", Age = 12 };
var objectJint = new JsObject(engine);
objectJint["name"] = "name";
objectJint["age"] = 12;
objectJint[GlobalSymbolRegistry.ToStringTag] = "Object";

var dictionaryNet = new Dictionary<JsValue, JsValue>();
dictionaryNet["name"] = "name";
dictionaryNet["age"] = 12;
dictionaryNet[GlobalSymbolRegistry.ToStringTag] = "Object";
}

[Fact]
public void CanAccessMemberNamedItemThroughExpando()
{
Expand Down
21 changes: 20 additions & 1 deletion Jint/Engine.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Esprima;
using Esprima.Ast;
using Jint.Native;
Expand All @@ -24,6 +25,7 @@ namespace Jint
/// <summary>
/// Engine is the main API to JavaScript interpretation. Engine instances are not thread-safe.
/// </summary>
[DebuggerTypeProxy(typeof(EngineDebugView))]
public sealed partial class Engine : IDisposable
{
private static readonly Options _defaultEngineOptions = new();
Expand Down Expand Up @@ -1575,5 +1577,22 @@ public void Dispose()
clearMethod?.Invoke(_objectWrapperCache, Array.Empty<object>());
#endif
}

[DebuggerDisplay("Engine")]
private sealed class EngineDebugView
{
private readonly Engine _engine;

public EngineDebugView(Engine engine)
{
_engine = engine;
}

public ObjectInstance Globals => _engine.Realm.GlobalObject;
public Options Options => _engine.Options;

public EnvironmentRecord VariableEnvironment => _engine.ExecutionContext.VariableEnvironment;
public EnvironmentRecord LexicalEnvironment => _engine.ExecutionContext.LexicalEnvironment;
}
}
}
2 changes: 2 additions & 0 deletions Jint/Native/Function/FunctionInstance.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Esprima.Ast;
using Jint.Native.Object;
Expand All @@ -9,6 +10,7 @@

namespace Jint.Native.Function
{
[DebuggerDisplay("{ToString(),nq}")]
public abstract partial class FunctionInstance : ObjectInstance, ICallable
{
protected PropertyDescriptor? _prototypeDescriptor;
Expand Down
28 changes: 28 additions & 0 deletions Jint/Native/JsArray.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using System.Diagnostics;
using Jint.Native.Array;

namespace Jint.Native;

[DebuggerTypeProxy(typeof(JsArrayDebugView))]
[DebuggerDisplay("Count = {Length}")]
public sealed class JsArray : ArrayInstance
{
/// <summary>
Expand All @@ -21,4 +24,29 @@ public JsArray(Engine engine, uint capacity = 0, uint length = 0) : base(engine,
public JsArray(Engine engine, JsValue[] items) : base(engine, items)
{
}

private sealed class JsArrayDebugView
{
private readonly JsArray _array;

public JsArrayDebugView(JsArray array)
{
_array = array;
}

[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public JsValue[] Values
{
get
{
var values = new JsValue[_array.Length];
var i = 0;
foreach (var value in _array)
{
values[i++] = value;
}
return values;
}
}
}
}
3 changes: 3 additions & 0 deletions Jint/Native/JsNumber.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using Jint.Native.Number;
using Jint.Runtime;

namespace Jint.Native;

[DebuggerDisplay("{_value}", Type = "string")]
public sealed class JsNumber : JsValue, IEquatable<JsNumber>
{
// .NET double epsilon and JS epsilon have different values
internal const double JavaScriptEpsilon = 2.2204460492503130808472633361816E-16;

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal readonly double _value;

// how many decimals to check when determining if double is actually an int
Expand Down
3 changes: 3 additions & 0 deletions Jint/Native/JsString.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System.Diagnostics;
using System.Text;
using Jint.Runtime;

namespace Jint.Native;

[DebuggerDisplay("{ToString()}")]
public class JsString : JsValue, IEquatable<JsString>, IEquatable<string>
{
private const int AsciiMax = 126;
Expand All @@ -28,6 +30,7 @@ public class JsString : JsValue, IEquatable<JsString>, IEquatable<string>
internal static readonly JsString LengthString = new JsString("length");
internal static readonly JsValue CommaString = new JsString(",");

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal string _value;

static JsString()
Expand Down
Loading

0 comments on commit adeb772

Please sign in to comment.