Skip to content

Commit

Permalink
Added support for x:Array extension, including inline syntax.
Browse files Browse the repository at this point in the history
  • Loading branch information
jp2masa committed Sep 23, 2020
1 parent 697a419 commit 13f7d6e
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 4 deletions.
74 changes: 73 additions & 1 deletion src/XamlX/Ast/Intrinsics.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Runtime.Serialization;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
Expand Down Expand Up @@ -136,6 +136,78 @@ IXamlType ResolveReturnType()
public IXamlAstTypeReference Type => new XamlAstClrTypeReference(this, ResolveReturnType(), false);
}

#if !XAMLX_INTERNAL
public
#endif
class XamlArrayExtensionNode : XamlAstNode, IXamlAstValueNode, IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
{
private IXamlAstValueNode _elementType;
private XamlAstClrTypeReference _type;

public XamlArrayExtensionNode(
XamlAstObjectNode lineInfo,
IXamlAstValueNode elementType,
IList<IXamlAstValueNode> elements)
: base(lineInfo)
{
_elementType = elementType;

Elements = elements;
}

public IXamlAstTypeReference Type =>
_type ?? throw new XamlParseException("Could not resolve x:Array Type for an unknown reason! Try using x:Type to specify the array element type.", this);

public IList<IXamlAstValueNode> Elements { get; }

public override void VisitChildren(Visitor visitor)
{
_elementType = (IXamlAstValueNode)_elementType.Visit(visitor);

if (_elementType is XamlTypeExtensionNode n && n.Value is XamlAstClrTypeReference clr)
{
_type = new XamlAstClrTypeReference(this, clr.Type.MakeArrayType(1), false);
}

for (int i = 0; i < Elements.Count; i++)
{
Elements[i] = (IXamlAstValueNode)Elements[i].Visit(visitor);
}
}

public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var type = _type.Type;
var elementType = type.ArrayElementType;

codeGen
.Ldc_I4(Elements.Count)
.Newarr(elementType);

for (int i = 0; i < Elements.Count; i++)
{
var element = Elements[i];

codeGen
.Dup()
.Ldc_I4(i);

context.Emit(element, codeGen, elementType);

if (elementType.IsValueType)
{
codeGen.Stelem(elementType);
}
else
{
codeGen.Stelem_ref();
}
}

return XamlILNodeEmitResult.Type(0, type);
}
}

#if !XAMLX_INTERNAL
public
#endif
Expand Down
3 changes: 2 additions & 1 deletion src/XamlX/Compiler/XamlCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public XamlCompiler(TransformerConfiguration configuration,
new ResolveContentPropertyTransformer(),
new ResolvePropertyValueAddersTransformer(),
new ConvertPropertyValuesToAssignmentsTransformer(),
new ConstructableObjectTransformer()
new ConstructableObjectTransformer(),
new ArrayElementTransformer()
};
SimplificationTransformers = new List<IXamlAstTransformer>
{
Expand Down
3 changes: 3 additions & 0 deletions src/XamlX/IL/XamlILEmitterExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ public static IXamlILEmitter Newarr(this IXamlILEmitter emitter, IXamlType type)

public static IXamlILEmitter Ldelem_ref(this IXamlILEmitter emitter) => emitter.Emit(OpCodes.Ldelem_Ref);
public static IXamlILEmitter Stelem_ref(this IXamlILEmitter emitter) => emitter.Emit(OpCodes.Stelem_Ref);

public static IXamlILEmitter Stelem(this IXamlILEmitter emitter, IXamlType type) => emitter.Emit(OpCodes.Stelem, type);

public static IXamlILEmitter Ldlen(this IXamlILEmitter emitter) => emitter.Emit(OpCodes.Ldlen);

public static IXamlILEmitter Add(this IXamlILEmitter emitter) => emitter.Emit(OpCodes.Add);
Expand Down
34 changes: 34 additions & 0 deletions src/XamlX/Transform/Transformers/ArrayElementTransformer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using XamlX.Ast;
using XamlX.TypeSystem;

namespace XamlX.Transform.Transformers
{
#if !XAMLX_INTERNAL
public
#endif
class ArrayElementTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlArrayExtensionNode array)
{
var elementType = array.Type.GetClrType().ArrayElementType;

for (int i = 0; i < array.Elements.Count; i++)
{
var element = array.Elements[i];

if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, element, elementType, out var converted))
{
throw new XamlLoadException(
$"Unable to convert value {element} to x:Array element type {elementType.GetFqn()}", element);
}

array.Elements[i] = converted;
}
}

return node;
}
}
}
51 changes: 49 additions & 2 deletions src/XamlX/Transform/Transformers/XamlIntrinsicsTransformer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using XamlX.Ast;

using System;
using System.Collections.Generic;
using System.Linq;

using XamlX.Ast;

namespace XamlX.Transform.Transformers
{
#if !XAMLX_INTERNAL
Expand Down Expand Up @@ -87,6 +91,49 @@ XamlAstTextNode ResolveArgumentOrValue(string extension, string name)
new XamlAstXmlTypeReference(ni, resolvedNs, tmpair[0], xml.GenericArguments),
tmpair[1]);
}

if (xml.Name == "Array")
{
var elements = new List<IXamlAstValueNode>();
elements.AddRange(ni.Arguments);

IXamlAstValueNode type = null;

foreach (var child in ni.Children)
{
if (child is XamlAstXamlPropertyValueNode propertyNode)
{
if (propertyNode.Property is XamlAstNamePropertyReference reference)
{
if (reference.Name.Equals("Type", StringComparison.Ordinal))
{
if (type is object)
{
throw new XamlParseException("Duplicate Type property on x:Array!", ni);
}

if (propertyNode.Values.Count != 1)
{
throw new XamlParseException("The Type property on x:Array should have a single value!", ni);
}

type = propertyNode.Values.Single();
}
}
}
else if (child is IXamlAstValueNode valueNode)
{
elements.Add(valueNode);
}
}

if (type is null)
{
throw new XamlParseException("Required Type property on x:Array not found!", ni);
}

return new XamlArrayExtensionNode(ni, type, elements);
}
}

return node;
Expand Down
39 changes: 39 additions & 0 deletions tests/XamlParserTests/IntrinsicsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,44 @@ public void Static_Extension_Resolves_Enum_Values()
{
Static_Extension_Resolves_Values(IntrinsicsTestsEnum.Foo, "IntrinsicsTestsEnum.Foo");
}

[Fact]
public void Array_Extension()
{
var res = (IntrinsicsTestsClass)CompileAndRun(@"
<IntrinsicsTestsClass
xmlns='test'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<IntrinsicsTestsClass.ObjectProperty>
<x:Array Type='{x:Type x:Object}'>
<x:Object>A</x:Object>
<x:String>B</x:String>
<x:Int32>42</x:Int32>
</x:Array>
</IntrinsicsTestsClass.ObjectProperty>
</IntrinsicsTestsClass>");

var array = (object[])res.ObjectProperty;

Assert.Equal("A", array[0]);
Assert.Equal("B", array[1]);
Assert.Equal(42, array[2]);
}

[Fact]
public void Array_Extension_Inline()
{
var res = (IntrinsicsTestsClass)CompileAndRun(@"
<IntrinsicsTestsClass
xmlns='test'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
ObjectProperty='{x:Array 3, 5, 0, Type={x:Type x:Int32}}' />");

var array = (int[])res.ObjectProperty;

Assert.Equal(3, array[0]);
Assert.Equal(5, array[1]);
Assert.Equal(0, array[2]);
}
}
}

0 comments on commit 13f7d6e

Please sign in to comment.