Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for x:Array extension, including inline syntax #28

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
42 changes: 41 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,46 @@ IXamlType ResolveReturnType()
public IXamlAstTypeReference Type => new XamlAstClrTypeReference(this, ResolveReturnType(), false);
}

#if !XAMLX_INTERNAL
public
#endif
class XamlArrayExtensionNode : XamlAstNode, IXamlAstValueNode
{
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);
}
}
}

#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
54 changes: 54 additions & 0 deletions src/XamlX/IL/Emitters/ArrayEmitter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using XamlX.Ast;
using XamlX.Emit;
using XamlX.TypeSystem;

namespace XamlX.IL.Emitters
{
#if !XAMLX_INTERNAL
public
#endif
class ArrayEmitter : IXamlAstNodeEmitter<IXamlILEmitter, XamlILNodeEmitResult>
{
public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
if (!(node is XamlArrayExtensionNode arrayNode))
{
return null;
}

var type = arrayNode.Type.GetClrType();
var elementType = type.ArrayElementType;

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

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

if (!elementType.IsAssignableFrom(element.Type.GetClrType()))
{
throw new XamlLoadException("x:Array element is not assignable to the array element type!", element);
}

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);
}
}
}
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
3 changes: 2 additions & 1 deletion src/XamlX/IL/XamlIlCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public XamlILCompiler(
new ManipulationGroupEmitter(),
new ValueWithManipulationsEmitter(),
new MarkupExtensionEmitter(),
new ObjectInitializationNodeEmitter()
new ObjectInitializationNodeEmitter(),
new ArrayEmitter()
});
}
}
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;
}
}
}
57 changes: 55 additions & 2 deletions src/XamlX/Transform/Transformers/XamlIntrinsicsTransformer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using XamlX.Ast;

using System;
using System.Collections.Generic;

using XamlX.Ast;

namespace XamlX.Transform.Transformers
{
#if !XAMLX_INTERNAL
Expand Down Expand Up @@ -87,6 +90,56 @@ 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>(ni.Arguments);

IXamlAstValueNode type = null;

foreach (var child in ni.Children)
{
if (child is XamlAstXamlPropertyValueNode propertyNode)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to trigger an error for unhandled nodes, otherwise they will be silently ignored.

{
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[0];
}
else
{
throw new XamlParseException($"Unknown property '{reference.Name}' in x:Array!", ni);
}
}
}
else if (child is IXamlAstValueNode valueNode)
{
elements.Add(valueNode);
}
else
{
context.ParseError("Invalid child node on x:Array!", child);
}
}

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]);
}
}
}