Skip to content

Commit cd10827

Browse files
authored
Feature/json evaluator (#47)
* prototype json eval * Improved json serialization * Add more natvis members to be excluded from C++ json serialization * Utils: cleanup unused code * Json serialization: initial version
1 parent 9dc00f1 commit cd10827

11 files changed

+480
-533
lines changed

VSDebugCoreLib/Commands/Debugging/ExportSymbol.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ namespace VSDebugCoreLib.Commands.Debugging
1010
{
1111
public class ExportSymbol : BaseCommand
1212
{
13-
private readonly DebuggerExpressionEvaluator _evaluator;
14-
1513
private const char TknForce = 'f';
1614
private const char TknAppend = 'a';
15+
private const char TknJson = 'j'; // New JSON flag
1716

1817
public ExportSymbol(VSDebugContext context)
1918
: base(context, (int)PkgCmdIDList.CmdIDAbout, "export")
@@ -24,12 +23,11 @@ public ExportSymbol(VSDebugContext context)
2423
"\tFlags:\n" +
2524
$"\t\t -{TknForce}\t- Force file overwrite.\n" +
2625
$"\t\t -{TknAppend}\t- Append to the file.\n" +
26+
$"\t\t -{TknJson}\t- Export result in JSON format.\n" + // Updated help string
2727
"\t<filename> \t- output filename\n" +
2828
"\t<expression> \t- symbol to evaluate\n" +
2929
"\tExample: export output.txt myVariable\n" +
3030
$"\tExample: export -{TknForce} output.txt myObject";
31-
32-
_evaluator = new DebuggerExpressionEvaluator(context.IDE);
3331
}
3432

3533
public override void Execute(string text)
@@ -48,6 +46,7 @@ public override void Execute(string text)
4846
// Parse options
4947
bool forceOverwrite = false;
5048
bool appendToFile = false;
49+
bool jsonMode = false; // New JSON mode flag
5150
string filename;
5251
string expression;
5352

@@ -58,6 +57,7 @@ public override void Execute(string text)
5857
string options = argv[0].Substring(1).ToLower();
5958
forceOverwrite = options.Contains(TknForce);
6059
appendToFile = options.Contains(TknAppend);
60+
jsonMode = options.Contains(TknJson); // Parse JSON flag
6161
argIndex++;
6262
}
6363

@@ -70,13 +70,16 @@ public override void Execute(string text)
7070
filename = argv[argIndex++];
7171
expression = argv[argIndex];
7272

73+
// Create the evaluator based on the current language
74+
var _evaluator = DebugHelpers.CreateEvaluator(Context.IDE);
75+
7376
try
7477
{
75-
string result = _evaluator.EvaluateExpression(expression);
78+
string result = _evaluator.EvaluateExpression(expression, jsonMode); // Pass JSON mode
7679

7780
// Ensure the directory exists
7881
string directory = Path.GetDirectoryName(filename);
79-
if (!string.IsNullOrEmpty(directory) )
82+
if (!string.IsNullOrEmpty(directory))
8083
{
8184
if (!Directory.Exists(directory)) Directory.CreateDirectory(directory);
8285
}

VSDebugCoreLib/Commands/Debugging/PrintSymbol.cs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,19 @@ namespace VSDebugCoreLib.Commands.Debugging
99
{
1010
public class PrintSymbol : BaseCommand
1111
{
12-
private readonly DebuggerExpressionEvaluator _evaluator;
12+
private const char TknJson = 'j'; // New JSON flag
1313

1414
public PrintSymbol(VSDebugContext context)
1515
: base(context, (int)PkgCmdIDList.CmdIDAbout, "print")
1616
{
1717
CommandDescription = "Evaluates and prints the value of a symbol or expression.";
18-
CommandHelpString = "Syntax: print <expression>\n" +
18+
CommandHelpString = "Syntax: print <optional flags> <expression>\n" +
1919
"\tEvaluates <expression> and prints its value.\n" +
20-
"\tExample: print myVariable\n";
21-
22-
_evaluator = new DebuggerExpressionEvaluator(context.IDE);
20+
"\tFlags:\n" +
21+
$"\t\t -{TknJson}\t- Print result in JSON format.\n" + // Updated help string
22+
"\t<expression> \t- symbol to evaluate\n" +
23+
"\tExample: print myVariable\n" +
24+
$"\tExample: print -{TknJson} myObject";
2325
}
2426

2527
public override void Execute(string text)
@@ -32,11 +34,27 @@ public override void Execute(string text)
3234
return;
3335
}
3436

35-
string expression = text;
37+
// Parse options
38+
bool jsonMode = false; // New JSON mode flag
39+
string expression;
40+
41+
if (text.StartsWith("-"))
42+
{
43+
string options = text.Substring(1, 1).ToLower();
44+
jsonMode = options.Contains(TknJson); // Parse JSON flag
45+
expression = text.Substring(3).Trim();
46+
}
47+
else
48+
{
49+
expression = text;
50+
}
51+
52+
// Create the evaluator based on the current language
53+
var _evaluator = DebugHelpers.CreateEvaluator(Context.IDE);
3654

3755
try
3856
{
39-
string result = _evaluator.EvaluateExpression(expression);
57+
string result = _evaluator.EvaluateExpression(expression, jsonMode); // Pass JSON mode
4058
Context.ConsoleEngine.Write(result);
4159
}
4260
catch (Exception ex)
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text.RegularExpressions;
5+
using EnvDTE;
6+
using EnvDTE80;
7+
using Newtonsoft.Json;
8+
using Newtonsoft.Json.Linq;
9+
10+
namespace VSDebugCoreLib.Utils
11+
{
12+
public class CppDebuggerExpressionEvaluator : DebuggerExpressionEvaluator
13+
{
14+
public CppDebuggerExpressionEvaluator(DTE2 dte) : base(dte) { }
15+
16+
protected override string FullyExpandExpressionJson(Expression rootExpr)
17+
{
18+
return JsonConvert.SerializeObject(ExpandExpressionToObject(rootExpr), Formatting.Indented);
19+
}
20+
21+
private JToken ExpandExpressionToObject(Expression rootExpr)
22+
{
23+
var stack = new Stack<(EnvDTE.Expression Expr, int Depth, JToken Parent, string Key)>();
24+
var root = new JObject();
25+
26+
stack.Push((rootExpr, 0, root, rootExpr.Name));
27+
28+
while (stack.Count > 0)
29+
{
30+
var (expr, depth, parent, key) = stack.Pop();
31+
32+
if (IsPrimitiveType(expr))
33+
{
34+
SetJsonValue(parent, TrimQuotes(key), new JValue(ParsePrimitiveValue(TrimQuotes(expr.Value))));
35+
}
36+
else if (IsDictionary(expr))
37+
{
38+
var node = new JObject();
39+
SetJsonValue(parent, TrimQuotes(key), node);
40+
41+
for (int i = expr.DataMembers.Count; i > 0; i--)
42+
{
43+
Expression childExpr = expr.DataMembers.Item(i);
44+
if (!FilterDataMember(expr, childExpr))
45+
{
46+
if (childExpr.Name.StartsWith("[") && childExpr.Name.EndsWith("]"))
47+
{
48+
var keyExpr = childExpr.DataMembers.Item(1); // "first" member
49+
var valueExpr = childExpr.DataMembers.Item(2); // "second" member
50+
stack.Push((valueExpr, depth + 1, node, keyExpr.Value.Trim('"', '\'')));
51+
}
52+
}
53+
}
54+
}
55+
else if (IsCollection(expr))
56+
{
57+
var node = new JArray();
58+
SetJsonValue(parent, TrimQuotes(key), node);
59+
60+
for (int i = expr.DataMembers.Count; i > 0; i--)
61+
{
62+
Expression childExpr = expr.DataMembers.Item(i);
63+
if (!FilterDataMember(expr, childExpr))
64+
{
65+
stack.Push((childExpr, depth + 1, node, TrimQuotes(RemoveBrackets(childExpr.Name))));
66+
}
67+
}
68+
}
69+
else if (IsPair(expr))
70+
{
71+
var node = new JObject();
72+
SetJsonValue(parent, TrimQuotes(key), node);
73+
74+
if (expr.DataMembers.Count >= 2)
75+
{
76+
stack.Push((expr.DataMembers.Item(1), depth + 1, node, "first"));
77+
stack.Push((expr.DataMembers.Item(2), depth + 1, node, "second"));
78+
}
79+
}
80+
else // Custom object or other complex type
81+
{
82+
var node = new JObject();
83+
SetJsonValue(parent, TrimQuotes(key), node);
84+
85+
for (int i = 1; i <= expr.DataMembers.Count; i++)
86+
{
87+
Expression childExpr = expr.DataMembers.Item(i);
88+
if (!FilterDataMember(expr, childExpr))
89+
{
90+
stack.Push((childExpr, depth + 1, node, childExpr.Name));
91+
}
92+
}
93+
}
94+
95+
// Limit expansion depth to prevent potential issues
96+
if (depth > MAX_DEPTH)
97+
{
98+
SetJsonValue(parent, TrimQuotes(key), new JValue("<maximum depth reached>"));
99+
continue;
100+
}
101+
}
102+
103+
return root[rootExpr.Name];
104+
}
105+
106+
protected override bool IsPrimitiveType(EnvDTE.Expression expr)
107+
{
108+
if (expr.DataMembers.Count == 0)
109+
return true;
110+
111+
string[] primitiveTypes = { "int", "float", "double", "str", "bool", "char", "number", "string", "boolean" };
112+
return primitiveTypes.Any(type => expr.Type.ToLower().Contains(type)) &&
113+
!expr.Type.ToLower().Contains("[]") &&
114+
!expr.Type.ToLower().Contains("list") &&
115+
!expr.Type.ToLower().Contains("<") && !expr.Type.ToLower().Contains(">") &&
116+
!IsCollection(expr);
117+
}
118+
119+
protected override bool IsCollection(EnvDTE.Expression expr)
120+
{
121+
string[] collectionTypes = { "array", "list", "set", "dictionary", "map", "tuple", "vector" };
122+
return collectionTypes.Any(type => expr.Type.ToLower().Contains(type)) ||
123+
expr.Value.Trim().StartsWith("[") ||
124+
(expr.DataMembers.Count > 0 && expr.DataMembers.Item(1).Name == "[0]") ||
125+
Regex.IsMatch(expr.Value, @"^\{\s*size\s*=\s*\d+\s*\}$");
126+
}
127+
128+
protected override bool IsDictionary(EnvDTE.Expression expr)
129+
{
130+
131+
string type = expr.Type.Trim();
132+
133+
// Use regex to match exact types or types with generic parameters
134+
if (Regex.IsMatch(type, @"^(Dictionary|Map|dict|Hashtable)(<.*>)?$", RegexOptions.IgnoreCase) ||
135+
Regex.IsMatch(type, @"^std::(unordered_)?map<.*>$", RegexOptions.IgnoreCase) ||
136+
Regex.IsMatch(type, @"^std::(unordered_)?multimap<.*>$", RegexOptions.IgnoreCase))
137+
{
138+
return true;
139+
}
140+
141+
return false;
142+
}
143+
144+
private bool IsPair(Expression expr)
145+
{
146+
return expr.Type.StartsWith("std::pair<") || expr.Type.StartsWith("pair<");
147+
}
148+
149+
protected override bool FilterDataMember(Expression parent, Expression child)
150+
{
151+
// filter banned nodes
152+
if (ExcludedMembers.Contains(child.Name))
153+
{
154+
return true;
155+
}
156+
157+
// C++ filter unordered_ natvis members
158+
if (Regex.IsMatch(parent.Type, @"^std::unordered_(map|multimap|set|multiset)<.*>$", RegexOptions.IgnoreCase))
159+
{
160+
string[] ExcludeKeywords = { "[hash_function]", "[key_eq]" };
161+
if (ExcludeKeywords.Contains(child.Name))
162+
{
163+
return true;
164+
}
165+
}
166+
// C++ filter variant index
167+
if (Regex.IsMatch(parent.Type, @"^std::variant<.*>$", RegexOptions.IgnoreCase))
168+
{
169+
string[] ExcludeKeywords = { "index" };
170+
if (ExcludeKeywords.Contains(child.Name))
171+
{
172+
return true;
173+
}
174+
}
175+
// C++ filter shared_ptr meta members
176+
if (Regex.IsMatch(parent.Type, @"^std::shared_ptr<.*>$", RegexOptions.IgnoreCase))
177+
{
178+
string[] ExcludeKeywords = { "[control block]" };
179+
if (ExcludeKeywords.Contains(child.Name))
180+
{
181+
return true;
182+
}
183+
}
184+
// C++ filter unique_ptr meta members
185+
if (Regex.IsMatch(parent.Type, @"^std::unique_ptr<.*>$", RegexOptions.IgnoreCase))
186+
{
187+
string[] ExcludeKeywords = { "[deleter]" };
188+
if (ExcludeKeywords.Contains(child.Name))
189+
{
190+
return true;
191+
}
192+
}
193+
194+
return false;
195+
}
196+
197+
private void SetJsonValue(JToken parent, string key, JToken value)
198+
{
199+
if (parent is JObject jObj)
200+
{
201+
jObj[key] = value;
202+
}
203+
else if (parent is JArray jArr)
204+
{
205+
jArr.Add(value);
206+
}
207+
}
208+
209+
private string RemoveBrackets(string input)
210+
{
211+
if (string.IsNullOrEmpty(input))
212+
{
213+
return input;
214+
}
215+
216+
// Remove leading and trailing whitespace
217+
input = input.Trim();
218+
219+
// Check if the string starts with '[' and ends with ']'
220+
if (input.StartsWith("[") && input.EndsWith("]"))
221+
{
222+
// Remove the first and last character
223+
return input.Substring(1, input.Length - 2);
224+
}
225+
226+
// If the input doesn't have brackets, return it as is
227+
return input;
228+
}
229+
}
230+
}

0 commit comments

Comments
 (0)