Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 76 additions & 18 deletions src/TemplateEngine/Microsoft.TemplateEngine.Core/Matching/Trie.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,33 @@ namespace Microsoft.TemplateEngine.Core.Matching
public class Trie<T>
where T : TerminalBase
{
private byte _singleNextNodeMatch;
private TrieNode<T>? _singleNextNode;
private Dictionary<byte, TrieNode<T>>? _nextNodes;

public Trie()
{
NextNodes = new Dictionary<byte, TrieNode<T>>();
}

public Dictionary<byte, TrieNode<T>> NextNodes { get; }
public Dictionary<byte, TrieNode<T>> NextNodes
{
get
{
Comment thread
JeremyKuhne marked this conversation as resolved.
Dictionary<byte, TrieNode<T>>? nextNodes = _nextNodes;
if (nextNodes != null)
{
return nextNodes;
}

nextNodes = new Dictionary<byte, TrieNode<T>>();
if (_singleNextNode != null)
{
nextNodes.Add(_singleNextNodeMatch, _singleNextNode);
}

return Interlocked.CompareExchange(ref _nextNodes, nextNodes, null) ?? nextNodes;
}
}

public int MaxRemainingLength { get; private set; }

Expand All @@ -23,23 +44,10 @@ public void AddPath(byte[] path, T terminal)
}

int remainingLength = path.Length - 1;
Dictionary<byte, TrieNode<T>>? current = NextNodes;
Trie<T> current = this;
for (int i = 0; i < path.Length; ++i, --remainingLength)
{
if (!current.TryGetValue(path[i], out TrieNode<T> next))
{
current[path[i]] = next = new TrieNode<T>(path[i])
{
MaxRemainingLength = remainingLength
};
}
else
{
if (next.MaxRemainingLength < remainingLength)
{
next.MaxRemainingLength = remainingLength;
}
}
TrieNode<T> next = current.GetOrAddNextNode(path[i], remainingLength);

if (i == path.Length - 1)
{
Expand All @@ -58,8 +66,58 @@ public void AddPath(byte[] path, T terminal)
}
}

current = next.NextNodes;
current = next;
}
}

internal bool TryGetNextNode(byte match, out TrieNode<T> next)
{
if (_nextNodes != null)
{
return _nextNodes.TryGetValue(match, out next!);
}

if (_singleNextNode != null && _singleNextNodeMatch == match)
{
next = _singleNextNode;
return true;
}

next = null!;
return false;
}

private TrieNode<T> GetOrAddNextNode(byte match, int remainingLength)
{
TrieNode<T> next;
if (_nextNodes != null)
{
if (!_nextNodes.TryGetValue(match, out next!))
{
_nextNodes.Add(match, next = new TrieNode<T>(match));
}
}
else if (_singleNextNode == null)
{
_singleNextNodeMatch = match;
_singleNextNode = next = new TrieNode<T>(match);
}
else if (_singleNextNodeMatch == match)
{
next = _singleNextNode;
}
else
{
next = new TrieNode<T>(match);
NextNodes.Add(match, next);
}

if (next.MaxRemainingLength < remainingLength)
{
next.MaxRemainingLength = remainingLength;
}

return next;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public bool Accept(byte data, ref int sequenceNumber, out TerminalLocation<T>? t
{
//If we matched another byte, advance the current node in
// the path and log the encountered terminal (if applicable)
if (path.CurrentNode.NextNodes.TryGetValue(data, out next))
if (path.CurrentNode.TryGetNextNode(data, out next))
{
path.CurrentNode = next;

Expand All @@ -121,7 +121,7 @@ public bool Accept(byte data, ref int sequenceNumber, out TerminalLocation<T>? t
}

//Try to start a new path in the trie
if (_trie.NextNodes.TryGetValue(data, out next))
if (_trie.TryGetNextNode(data, out next))
{
TriePath<T> path = new TriePath<T>(sequenceNumber)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text;
using Microsoft.TemplateEngine.Core.Matching;
using Microsoft.TemplateEngine.Core.Util;

namespace Microsoft.TemplateEngine.Core.UnitTests
Expand Down Expand Up @@ -83,6 +84,26 @@ public void VerifyTokenTrieAtBegin()
Assert.AreEqual(-1, token);
}

[TestMethod]
public void NextNodesContainsBranchesAndRemainsStable()
{
TokenTrie trie = new TokenTrie();
trie.AddToken("cat"u8.ToArray());
trie.AddToken("car"u8.ToArray());

Dictionary<byte, TrieNode<Token>> rootNodes = trie.NextNodes;
Assert.AreSame(rootNodes, trie.NextNodes);
Assert.IsTrue(rootNodes.TryGetValue((byte)'c', out TrieNode<Token>? cNode));
Assert.IsTrue(cNode.NextNodes.TryGetValue((byte)'a', out TrieNode<Token>? aNode));
Assert.HasCount(2, aNode.NextNodes);
Assert.IsTrue(aNode.NextNodes.ContainsKey((byte)'r'));
Assert.IsTrue(aNode.NextNodes.ContainsKey((byte)'t'));
Comment thread
JeremyKuhne marked this conversation as resolved.

trie.AddToken("dog"u8.ToArray());
Assert.HasCount(2, rootNodes);
Assert.IsTrue(rootNodes.ContainsKey((byte)'d'));
}

[TestMethod]
public void VerifyTokenTrieNotEnoughBufferLeft()
{
Expand Down
Loading