Add abstractions for Razor IR

This is an API skeleton for the IR data model that we'll be using as a
spiritual continuation of the 'chunks' API. Currently missing a lot of
detail which will be filled in as needed.
This commit is contained in:
Ryan Nowak 2016-10-27 10:46:05 -07:00 committed by N. Taylor Mullen
parent b74ea5d74e
commit d40f6d3151
12 changed files with 489 additions and 0 deletions

View File

@ -0,0 +1,75 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
internal class DefaultIRBuilder : IRBuilder
{
private readonly List<IRNode> _stack;
private int _depth;
public DefaultIRBuilder()
{
_stack = new List<IRNode>();
}
public override IRNode Current
{
get
{
return _depth > 0 ? _stack[_depth - 1] : null;
}
}
public override void Add(IRNode node)
{
if (node == null)
{
throw new ArgumentNullException(nameof(node));
}
Push(node);
Pop();
}
public override IRNode Pop()
{
if (_depth == 0)
{
throw new InvalidOperationException(Resources.FormatIRBuilder_PopInvalid(nameof(Pop)));
}
var node = _stack[--_depth];
return node;
}
public override void Push(IRNode node)
{
if (node == null)
{
throw new ArgumentNullException(nameof(node));
}
if (_depth >= _stack.Count)
{
_stack.Add(node);
}
else
{
_stack[_depth] = node;
}
if (_depth > 0)
{
var parent = _stack[_depth - 1];
node.Parent = parent;
parent.Children.Add(node);
}
_depth++;
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
public abstract class IRBuilder
{
public static IRBuilder Document()
{
var builder = new DefaultIRBuilder();
builder.Push(new IRDocument());
return builder;
}
public abstract IRNode Current { get; }
public abstract void Add(IRNode node);
public abstract void Push(IRNode node);
public abstract IRNode Pop();
}
}

View File

@ -0,0 +1,31 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
public sealed class IRDocument : IRNode
{
// Only allow creation of documents through the builder API because
// they can't be nested.
internal IRDocument()
{
Children = new List<IRNode>();
}
public override IList<IRNode> Children { get; }
public override IRNode Parent { get; set; }
public override void Accept(IRNodeVisitor visitor)
{
visitor.VisitDocument(this);
}
public override TResult Accept<TResult>(IRNodeVisitor<TResult> visitor)
{
return visitor.VisitDocument(this);
}
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
public abstract class IRNode
{
internal static readonly IRNode[] EmptyArray = new IRNode[0];
public abstract IList<IRNode> Children { get; }
public abstract IRNode Parent { get; set; }
public abstract void Accept(IRNodeVisitor visitor);
public abstract TResult Accept<TResult>(IRNodeVisitor<TResult> visitor);
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
public abstract class IRNodeVisitor
{
public virtual void Visit(IRNode node)
{
node.Accept(this);
}
public virtual void VisitDefault(IRNode node)
{
}
public virtual void VisitDocument(IRDocument node)
{
VisitDefault(node);
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
public abstract class IRNodeVisitor<TResult>
{
public virtual TResult Visit(IRNode node)
{
return node.Accept(this);
}
public virtual TResult VisitDefault(IRNode node)
{
return default(TResult);
}
public virtual TResult VisitDocument(IRDocument node)
{
return VisitDefault(node);
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
public abstract class IRNodeWalker : IRNodeVisitor
{
public override void VisitDefault(IRNode node)
{
var children = node.Children;
for (var i = 0; i < node.Children.Count; i++)
{
var child = children[i];
Visit(child);
}
}
}
}

View File

@ -10,6 +10,22 @@ namespace Microsoft.AspNetCore.Razor.Evolution
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNetCore.Razor.Evolution.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The '{0}' operation is not valid when the builder is empty.
/// </summary>
internal static string IRBuilder_PopInvalid
{
get { return GetString("IRBuilder_PopInvalid"); }
}
/// <summary>
/// The '{0}' operation is not valid when the builder is empty.
/// </summary>
internal static string FormatIRBuilder_PopInvalid(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("IRBuilder_PopInvalid"), p0);
}
/// <summary>
/// The '{0}' phase requires a '{1}' provided by the '{2}'.
/// </summary>

View File

@ -117,6 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="IRBuilder_PopInvalid" xml:space="preserve">
<value>The '{0}' operation is not valid when the builder is empty.</value>
</data>
<data name="PhaseDependencyMissing" xml:space="preserve">
<value>The '{0}' phase requires a '{1}' provided by the '{2}'.</value>
</data>

View File

@ -0,0 +1,145 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
public class DefaultIRBuilderTest
{
[Fact]
public void Ctor_CreatesEmptyBuilder()
{
// Arrange & Act
var builder = new DefaultIRBuilder();
var current = builder.Current;
// Assert
Assert.Null(current);
}
[Fact]
public void Push_WhenEmpty_AddsNode()
{
// Arrange
var builder = new DefaultIRBuilder();
var node = new BasicIRNode();
// Act
builder.Push(node);
// Assert
Assert.Same(node, builder.Current);
Assert.Null(node.Parent);
}
[Fact]
public void Push_WhenNonEmpty_SetsUpParentAndChild()
{
// Arrange
var builder = new DefaultIRBuilder();
var parent = new BasicIRNode();
builder.Push(parent);
var node = new BasicIRNode();
// Act
builder.Push(node);
// Assert
Assert.Same(node, builder.Current);
Assert.Same(parent, node.Parent);
Assert.Collection(parent.Children, n => Assert.Same(node, n));
}
[Fact]
public void Pop_ThrowsWhenEmpty()
{
// Arrange
var builder = new DefaultIRBuilder();
// Act & Assert
ExceptionAssert.Throws<InvalidOperationException>(
() => builder.Pop(),
"The 'Pop' operation is not valid when the builder is empty.");
}
[Fact]
public void Pop_SingleNodeDepth_RemovesAndReturnsNode()
{
// Arrange
var builder = new DefaultIRBuilder();
var node = new BasicIRNode();
builder.Push(node);
// Act
var result = builder.Pop();
// Assert
Assert.Same(node, result);
Assert.Null(builder.Current);
}
[Fact]
public void Pop_MultipleNodeDepth_RemovesAndReturnsNode()
{
// Arrange
var builder = new DefaultIRBuilder();
var parent = new BasicIRNode();
builder.Push(parent);
var node = new BasicIRNode();
builder.Push(node);
// Act
var result = builder.Pop();
// Assert
Assert.Same(node, result);
Assert.Same(parent, builder.Current);
}
[Fact]
public void Add_DoesPushAndPop()
{
// Arrange
var builder = new DefaultIRBuilder();
var parent = new BasicIRNode();
builder.Push(parent);
var node = new BasicIRNode();
// Act
builder.Add(node);
// Assert
Assert.Same(parent, builder.Current);
Assert.Same(parent, node.Parent);
Assert.Collection(parent.Children, n => Assert.Same(node, n));
}
private class BasicIRNode : IRNode
{
public override IList<IRNode> Children { get; } = new List<IRNode>();
public override IRNode Parent { get; set; }
public override void Accept(IRNodeVisitor visitor)
{
throw new NotImplementedException();
}
public override TResult Accept<TResult>(IRNodeVisitor<TResult> visitor)
{
throw new NotImplementedException();
}
}
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
public class IRBuilderTest
{
[Fact]
public void Document_CreatesDocumentNode()
{
// Arrange & Act
var builder = IRBuilder.Document();
// Assert
Assert.IsType<IRDocument>(builder.Current);
}
}
}

View File

@ -0,0 +1,93 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
public class IRNodeWalkerTest
{
[Fact]
public void IRNodeWalker_Visit_TraversesEntireGraph()
{
// Arrange
var walker = new DerivedIRNodeWalker();
var nodes = new IRNode[]
{
new BasicIRNode("Root"),
new BasicIRNode("Root->A"),
new BasicIRNode("Root->B"),
new BasicIRNode("Root->B->1"),
new BasicIRNode("Root->B->2"),
new BasicIRNode("Root->C"),
};
var builder = new DefaultIRBuilder();
builder.Push(nodes[0]);
builder.Add(nodes[1]);
builder.Push(nodes[2]);
builder.Add(nodes[3]);
builder.Add(nodes[4]);
builder.Pop();
builder.Add(nodes[5]);
var root = builder.Pop();
// Act
walker.Visit(root);
// Assert
Assert.Equal(nodes, walker.Visited.ToArray());
}
private class DerivedIRNodeWalker : IRNodeWalker
{
public List<IRNode> Visited { get; } = new List<IRNode>();
public override void VisitDefault(IRNode node)
{
Visited.Add(node);
base.VisitDefault(node);
}
public virtual void VisitBasic(BasicIRNode node)
{
VisitDefault(node);
}
}
private class BasicIRNode : IRNode
{
public BasicIRNode(string name)
{
Name = name;
}
public string Name { get; }
public override IList<IRNode> Children { get; } = new List<IRNode>();
public override IRNode Parent { get; set; }
public override void Accept(IRNodeVisitor visitor)
{
((DerivedIRNodeWalker)visitor).VisitBasic(this);
}
public override TResult Accept<TResult>(IRNodeVisitor<TResult> visitor)
{
throw new NotImplementedException();
}
public override string ToString()
{
return Name;
}
}
}
}