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
|
private static readonly ResourceManager _resourceManager
|
||||||
= new ResourceManager("Microsoft.AspNetCore.Razor.Evolution.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
= 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>
|
/// <summary>
|
||||||
/// The '{0}' phase requires a '{1}' provided by the '{2}'.
|
/// The '{0}' phase requires a '{1}' provided by the '{2}'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,9 @@
|
||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</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">
|
<data name="PhaseDependencyMissing" xml:space="preserve">
|
||||||
<value>The '{0}' phase requires a '{1}' provided by the '{2}'.</value>
|
<value>The '{0}' phase requires a '{1}' provided by the '{2}'.</value>
|
||||||
</data>
|
</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