Skip to content

Commit

Permalink
WIP #54 Runtime.Debuggee
Browse files Browse the repository at this point in the history
  • Loading branch information
zspitz committed Jul 9, 2021
1 parent 4fe4e31 commit 8a92ed3
Show file tree
Hide file tree
Showing 28 changed files with 1,458 additions and 1 deletion.
14 changes: 14 additions & 0 deletions Debuggee/Debuggee.projitems
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>cc83f717-2d1d-4b45-bead-be7fa88bb71c</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>Debuggee</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)VisualizerDataObjectSource.cs" />
</ItemGroup>
</Project>
13 changes: 13 additions & 0 deletions Debuggee/Debuggee.shproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>cc83f717-2d1d-4b45-bead-be7fa88bb71c</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<PropertyGroup />
<Import Project="Debuggee.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>
10 changes: 10 additions & 0 deletions Debuggee/VisualizerDataObjectSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Reflection;
using ParseTreeVisualizer.Serialization;
using Periscope.Debuggee;

namespace ParseTreeVisualizer {
public class VisualizerDataObjectSource : VisualizerObjectSourceBase<object, Config> {
public override object GetResponse(object target, Config config) => new VisualizerData(target, config);
public override string GetConfigKey(object target) => Assembly.GetAssembly(target.GetType()).GetName().Name;
}
}
Submodule Periscope.Debuggee updated from 000000 to fc461a
30 changes: 30 additions & 0 deletions Runtime.Debuggee/Runtime.Debuggee.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net452</TargetFramework>
<RootNamespace>ParseTreeVisualizer.Runtime.Debuggee</RootNamespace>
<AssemblyName>ParseTreeVisualizer.Runtime.Debuggee</AssemblyName>
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
<DefineConstants>VISUALIZER_DEBUGGEE, ANTLR_RUNTIME</DefineConstants>
</PropertyGroup>

<PropertyGroup>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<OutputPath>bin/$(Configuration)/net2.0/</OutputPath>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="ZSpitz.Util" Version="0.1.112" />
<Reference Include="Microsoft.VisualStudio.DebuggerVisualizers">
<HintPath>C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.DebuggerVisualizers.dll</HintPath>
<Private>false</Private>
</Reference>
<PackageReference Include="Antlr4.Runtime" Version="4.6.6" />
</ItemGroup>

<Import Project="..\Serialization\Serialization.projitems" Label="Serialization" />
<Import Project="..\Debuggee\Debuggee.projitems" Label="Debuggee" />
</Project>
6 changes: 6 additions & 0 deletions RuntimeStandard.Debuggee/Class1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using System;

namespace RuntimeStandard.Debuggee {
public class Class1 {
}
}
7 changes: 7 additions & 0 deletions RuntimeStandard.Debuggee/RuntimeStandard.Debuggee.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>

</Project>
54 changes: 54 additions & 0 deletions Serialization/ClassInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Antlr4.Runtime;
using Antlr4.Runtime.Tree;
using System;
using System.CodeDom.Compiler;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using ZSpitz.Util;

namespace ParseTreeVisualizer.Serialization {
[Serializable]
public class ClassInfo {
public static readonly ClassInfo None = new("(None)");

public string Name { get; }
public string? Namespace { get; }
public string? Assembly { get; }
public string? Antlr { get; private set; } // Runtime - in Antlr.Runtime, <number> - version
public string? FullName { get; }
public string? RuleName { get; }
public int? RuleID { get; }

public ReadOnlyCollection<string>? MethodNames { get; }

private ClassInfo(string name) => Name = name;
public ClassInfo(Type t, string? ruleName = null, int? ruleID = null, bool loadMethodNames = false) {
if (t is null) { throw new ArgumentNullException(nameof(t)); }

Name = t.Name;
Namespace = t.Namespace;
Assembly = t.Assembly.Location;
if (t.Assembly == typeof(IParseTree).Assembly) {
Antlr = "Runtime";
} else if (t.GetCustomAttribute<GeneratedCodeAttribute>() is var attr) {
Antlr = attr.Version;
}
FullName = t.FullName;
RuleName = ruleName.IsNullOrWhitespace() ? null : ruleName;
RuleID = ruleID;

if (loadMethodNames) {
MethodNames = t.GetMethods()
.Where(x => !x.IsSpecialName && x.ReturnType.InheritsFromOrImplements<ParserRuleContext>())
.Select(x => x.Name)
.Where(x => x != "GetInvokingContext")
.Ordered()
.ToList()
.AsReadOnly();
}
}

public override string ToString() => FullName ?? Name;
}
}
70 changes: 70 additions & 0 deletions Serialization/Config.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Periscope.Debuggee;
using System;
using System.Collections.Generic;
using ZSpitz.Util;

namespace ParseTreeVisualizer.Serialization {
[Serializable]
#if VISUALIZER_DEBUGGEE
public class Config : Periscope.Debuggee.ConfigBase<Config> {
#else
public class Config {
#endif

public string? SelectedParserName { get; set; }
public string? ParseTokensWithRule { get; set; }
public string? SelectedLexerName { get; set; }
public bool ShowTextTokens { get; set; } = true;
public bool ShowWhitespaceTokens { get; set; } = true;
public bool ShowErrorTokens { get; set; } = true;
public HashSet<int> SelectedTokenTypes { get; } = new HashSet<int>();
public bool ShowTreeTextTokens { get; set; } = true;
public bool ShowTreeWhitespaceTokens { get; set; } = true;
public bool ShowTreeErrorTokens { get; set; } = true;
public bool ShowRuleContextNodes { get; set; } = true;
public HashSet<string?> SelectedRuleContexts { get; } = new HashSet<string?>();
public string? RootNodePath { get; set; }

public bool HasTreeFilter() => !(ShowTreeErrorTokens && ShowTreeWhitespaceTokens && ShowTreeTextTokens && ShowRuleContextNodes && SelectedRuleContexts.None());
public bool HasTokenListFilter() => !(ShowTextTokens && ShowErrorTokens && ShowWhitespaceTokens && SelectedTokenTypes.None());

#if VISUALIZER_DEBUGGEE
// TODO should any parts of the config return ConfigDiffStates.NeedsWrite?
public override ConfigDiffStates Diff(Config baseline) =>
(
baseline.SelectedParserName == SelectedParserName &&
baseline.SelectedLexerName == SelectedLexerName &&
baseline.ShowTextTokens == ShowTextTokens &&
baseline.ShowErrorTokens == ShowErrorTokens &&
baseline.ShowWhitespaceTokens == ShowWhitespaceTokens &&
baseline.ShowTreeTextTokens == ShowTreeTextTokens &&
baseline.ShowTreeErrorTokens == ShowTreeErrorTokens &&
baseline.ShowTreeWhitespaceTokens == ShowTreeWhitespaceTokens &&
baseline.ShowRuleContextNodes == ShowRuleContextNodes &&
baseline.ParseTokensWithRule == ParseTokensWithRule &&
baseline.SelectedTokenTypes.SetEquals(SelectedTokenTypes) &&
baseline.SelectedRuleContexts.SetEquals(SelectedRuleContexts)
) ? ConfigDiffStates.NoAction : ConfigDiffStates.NeedsTransfer;

public override Config Clone() {
#else
public Config Clone() {
#endif
var ret = new Config {
SelectedParserName = SelectedParserName,
SelectedLexerName = SelectedLexerName,
ShowTextTokens = ShowTextTokens,
ShowErrorTokens = ShowErrorTokens,
ShowWhitespaceTokens = ShowWhitespaceTokens,
ShowTreeTextTokens = ShowTreeTextTokens,
ShowTreeErrorTokens = ShowTreeErrorTokens,
ShowTreeWhitespaceTokens = ShowTreeWhitespaceTokens,
ShowRuleContextNodes = ShowRuleContextNodes,
ParseTokensWithRule = ParseTokensWithRule
};
SelectedTokenTypes.AddRangeTo(ret.SelectedTokenTypes);
SelectedRuleContexts.AddRangeTo(ret.SelectedRuleContexts);
return ret;
}
}
}
15 changes: 15 additions & 0 deletions Serialization/Enums.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace ParseTreeVisualizer.Serialization {
public enum TreeNodeType {
RuleContext,
Token,
ErrorToken,
WhitespaceToken,
Placeholder
}

public enum FilterStates {
NotMatched,
Matched,
DescendantMatched
}
}
36 changes: 36 additions & 0 deletions Serialization/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Antlr4.Runtime.Tree;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ParseTreeVisualizer {
public static class Extensions {
public static IEnumerable<IParseTree> Children(this IParseTree tree) {
for (var i = 0; i < tree.ChildCount; i++) {
yield return tree.GetChild(i);
}
}

public static IEnumerable<IParseTree> Descendants(this IParseTree tree) {
for (var i = 0; i < tree.ChildCount; i++) {
var child = tree.GetChild(i);
yield return child;
foreach (var descendant in child.Descendants()) {
yield return descendant;
}
}
}

public static string GetPositionedText(this IParseTree tree, char filler = ' ') {
var sb = new StringBuilder();
foreach (var descendant in tree.Descendants().OfType<TerminalNodeImpl>()) {
var fillerCharCount = descendant.Payload.StartIndex - sb.Length;
if (fillerCharCount > 0) {
sb.Append(filler, fillerCharCount);
}
sb.Append(descendant.Payload.Text);
}
return sb.ToString();
}
}
}
121 changes: 121 additions & 0 deletions Serialization/ParseTreeNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using Antlr4.Runtime;
using Antlr4.Runtime.Tree;
using System;
using System.Collections.Generic;
using System.Linq;
using ZSpitz.Util;

namespace ParseTreeVisualizer.Serialization {
[Serializable]
public class ParseTreeNode {
public static ParseTreeNode GetPlaceholder(ParseTreeNode? actualRoot) =>
actualRoot is null ?
throw new ArgumentNullException(nameof(actualRoot)) :
new("(parent nodes)", TreeNodeType.Placeholder, new List<ParseTreeNode> { actualRoot }, actualRoot.CharSpan);

public string? Caption { get; private set; }
public List<PropertyValue>? Properties { get; }
public List<ParseTreeNode> Children { get; private set; }
public (int startTokenIndex, int endTokenIndex) TokenSpan { get; }
public (int startChar, int endChar) CharSpan { get; private set; }
public TreeNodeType? NodeType { get; private set; }
public FilterStates? FilterState { get; }
public string? Path { get; }

private ParseTreeNode(string caption, TreeNodeType nodeType, List<ParseTreeNode> children, (int startChar, int endChar) charSpan) {
Caption = caption;
NodeType = nodeType;
Children = children;
CharSpan = charSpan;
}
public ParseTreeNode(IParseTree tree, List<Token> tokens, string[] ruleNames, Dictionary<int, string> tokenTypeMapping, Config config, Dictionary<Type, (string? caption, int? index)> ruleMapping, string? path) {
if (tree is null) { throw new ArgumentNullException(nameof(tree)); }
if (ruleMapping is null) { throw new ArgumentNullException(nameof(ruleMapping)); }
if (tokens is null) { throw new ArgumentNullException(nameof(tokens)); }
if (config is null) { throw new ArgumentNullException(nameof(config)); }

var type = tree.GetType();

if (tree is ParserRuleContext ruleContext) {
NodeType = TreeNodeType.RuleContext;

var caption = type.Name;
if (!ruleMapping.TryGetValue(type, out var x)) {
var ruleIndex = (int)(type.GetProperty("RuleIndex")?.GetValue(tree) ?? -1);
if (ruleNames.TryGetValue(ruleIndex, out caption)) {
ruleMapping[type] = (caption, ruleIndex);
} else {
caption = type.Name;
ruleMapping[type] = (null, null);
}
} else {
caption = x.caption;
}

Caption = caption;
CharSpan = (ruleContext.Start.StartIndex, ruleContext.Stop?.StopIndex ?? int.MaxValue);
} else if (tree is TerminalNodeImpl terminalNode) {
var token = new Token(terminalNode, tokenTypeMapping);

if (token.IsError) {
Caption = token.Text;
NodeType = TreeNodeType.ErrorToken;
} else {
Caption = $"\"{token.Text}\"";
NodeType = token.IsWhitespace ? TreeNodeType.WhitespaceToken : TreeNodeType.Token;
}
CharSpan = token.Span;

if (token.ShowToken(config)) {
tokens.Add(token);
}
}

Path = path;
var pathDelimiter = path.IsNullOrWhitespace() ? "" : ".";
Properties = type.GetProperties().OrderBy(x => x.Name).Select(prp => new PropertyValue(tree, prp)).ToList();
Children = tree.Children()
.Select((x, index) => new ParseTreeNode(x, tokens, ruleNames, tokenTypeMapping, config, ruleMapping, $"{path}{pathDelimiter}{index}"))
.Where(x => x.FilterState != FilterStates.NotMatched) // intentionally doesn't exclude null
.ToList();
TokenSpan = (tree.SourceInterval.a, tree.SourceInterval.b);

var matched = true;
if (config.HasTreeFilter()) {
matched = NodeType switch {
TreeNodeType.RuleContext =>
config.ShowRuleContextNodes && (
config.SelectedRuleContexts.None() ||
type.FullName.In(config.SelectedRuleContexts)
),
TreeNodeType.ErrorToken => config.ShowErrorTokens,
TreeNodeType.WhitespaceToken => config.ShowTreeWhitespaceTokens,
_ => config.ShowTreeTextTokens
};

FilterState =
matched ?
FilterStates.Matched :
(Children.Any(x => x.FilterState.In(FilterStates.Matched, FilterStates.DescendantMatched)) ?
FilterStates.DescendantMatched :
FilterStates.NotMatched);
}

var toPromote = Children
.Select((child, index) => (grandchild: child.Children.SingleOrDefaultExt(x => x.FilterState.In(FilterStates.Matched, FilterStates.DescendantMatched)), index))
.WhereT((grandchild, index) => grandchild != null)
.ToList();
foreach (var (grandchild, index) in toPromote) {
Children[index] = grandchild;
}
}

public string Stringify(int indentLevel = 0) {
var ret = new string(' ', indentLevel * 4) + Caption;
foreach (var child in Children) {
ret += "\n" + child.Stringify(indentLevel + 1);
}
return ret;
}
}
}
Loading

0 comments on commit 8a92ed3

Please sign in to comment.