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:
parent
b74ea5d74e
commit
d40f6d3151
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue