From 13f7d6eda80a1c9c6f55da79c022d356d56d65b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Wed, 23 Sep 2020 18:07:37 +0100 Subject: [PATCH] Added support for x:Array extension, including inline syntax. --- src/XamlX/Ast/Intrinsics.cs | 74 ++++++++++++++++++- src/XamlX/Compiler/XamlCompiler.cs | 3 +- src/XamlX/IL/XamlILEmitterExtensions.cs | 3 + .../Transformers/ArrayElementTransformer.cs | 34 +++++++++ .../Transformers/XamlIntrinsicsTransformer.cs | 51 ++++++++++++- tests/XamlParserTests/IntrinsicsTests.cs | 39 ++++++++++ 6 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 src/XamlX/Transform/Transformers/ArrayElementTransformer.cs diff --git a/src/XamlX/Ast/Intrinsics.cs b/src/XamlX/Ast/Intrinsics.cs index bf0336cf..15e2096a 100644 --- a/src/XamlX/Ast/Intrinsics.cs +++ b/src/XamlX/Ast/Intrinsics.cs @@ -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; @@ -136,6 +136,78 @@ IXamlType ResolveReturnType() public IXamlAstTypeReference Type => new XamlAstClrTypeReference(this, ResolveReturnType(), false); } +#if !XAMLX_INTERNAL + public +#endif + class XamlArrayExtensionNode : XamlAstNode, IXamlAstValueNode, IXamlAstEmitableNode + { + private IXamlAstValueNode _elementType; + private XamlAstClrTypeReference _type; + + public XamlArrayExtensionNode( + XamlAstObjectNode lineInfo, + IXamlAstValueNode elementType, + IList 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 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 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 diff --git a/src/XamlX/Compiler/XamlCompiler.cs b/src/XamlX/Compiler/XamlCompiler.cs index 3f4ca1d9..1ef0703f 100644 --- a/src/XamlX/Compiler/XamlCompiler.cs +++ b/src/XamlX/Compiler/XamlCompiler.cs @@ -43,7 +43,8 @@ public XamlCompiler(TransformerConfiguration configuration, new ResolveContentPropertyTransformer(), new ResolvePropertyValueAddersTransformer(), new ConvertPropertyValuesToAssignmentsTransformer(), - new ConstructableObjectTransformer() + new ConstructableObjectTransformer(), + new ArrayElementTransformer() }; SimplificationTransformers = new List { diff --git a/src/XamlX/IL/XamlILEmitterExtensions.cs b/src/XamlX/IL/XamlILEmitterExtensions.cs index 54bbfb45..60a7e774 100644 --- a/src/XamlX/IL/XamlILEmitterExtensions.cs +++ b/src/XamlX/IL/XamlILEmitterExtensions.cs @@ -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); diff --git a/src/XamlX/Transform/Transformers/ArrayElementTransformer.cs b/src/XamlX/Transform/Transformers/ArrayElementTransformer.cs new file mode 100644 index 00000000..d02b5322 --- /dev/null +++ b/src/XamlX/Transform/Transformers/ArrayElementTransformer.cs @@ -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; + } + } +} diff --git a/src/XamlX/Transform/Transformers/XamlIntrinsicsTransformer.cs b/src/XamlX/Transform/Transformers/XamlIntrinsicsTransformer.cs index 55014799..47ab3bae 100644 --- a/src/XamlX/Transform/Transformers/XamlIntrinsicsTransformer.cs +++ b/src/XamlX/Transform/Transformers/XamlIntrinsicsTransformer.cs @@ -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 @@ -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(); + 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; diff --git a/tests/XamlParserTests/IntrinsicsTests.cs b/tests/XamlParserTests/IntrinsicsTests.cs index b0798a83..76c56552 100644 --- a/tests/XamlParserTests/IntrinsicsTests.cs +++ b/tests/XamlParserTests/IntrinsicsTests.cs @@ -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(@" + + + + A + B + 42 + + +"); + + 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(@" +"); + + var array = (int[])res.ObjectProperty; + + Assert.Equal(3, array[0]); + Assert.Equal(5, array[1]); + Assert.Equal(0, array[2]); + } } } \ No newline at end of file