From f8f4ee4028237bc455ec611c52e6dfbf1fedb6fb 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 | 42 +++++++++++++- src/XamlX/Compiler/XamlCompiler.cs | 3 +- src/XamlX/IL/Emitters/ArrayEmitter.cs | 54 ++++++++++++++++++ src/XamlX/IL/XamlILEmitterExtensions.cs | 3 + src/XamlX/IL/XamlIlCompiler.cs | 3 +- .../Transformers/ArrayElementTransformer.cs | 34 +++++++++++ .../Transformers/XamlIntrinsicsTransformer.cs | 57 ++++++++++++++++++- tests/XamlParserTests/IntrinsicsTests.cs | 39 +++++++++++++ 8 files changed, 230 insertions(+), 5 deletions(-) create mode 100644 src/XamlX/IL/Emitters/ArrayEmitter.cs 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..0d36df95 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,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 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); + } + } + } + #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/Emitters/ArrayEmitter.cs b/src/XamlX/IL/Emitters/ArrayEmitter.cs new file mode 100644 index 00000000..4c427503 --- /dev/null +++ b/src/XamlX/IL/Emitters/ArrayEmitter.cs @@ -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 + { + public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContext 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); + } + } +} 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/IL/XamlIlCompiler.cs b/src/XamlX/IL/XamlIlCompiler.cs index ce0f84bd..c68ed9ca 100644 --- a/src/XamlX/IL/XamlIlCompiler.cs +++ b/src/XamlX/IL/XamlIlCompiler.cs @@ -39,7 +39,8 @@ public XamlILCompiler( new ManipulationGroupEmitter(), new ValueWithManipulationsEmitter(), new MarkupExtensionEmitter(), - new ObjectInitializationNodeEmitter() + new ObjectInitializationNodeEmitter(), + new ArrayEmitter() }); } } 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..c670ccf0 100644 --- a/src/XamlX/Transform/Transformers/XamlIntrinsicsTransformer.cs +++ b/src/XamlX/Transform/Transformers/XamlIntrinsicsTransformer.cs @@ -1,5 +1,8 @@ -using XamlX.Ast; - +using System; +using System.Collections.Generic; + +using XamlX.Ast; + namespace XamlX.Transform.Transformers { #if !XAMLX_INTERNAL @@ -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(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[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; 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