8 changed files with 355 additions and 215 deletions
@ -0,0 +1,205 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reflection.Emit; |
|||
using XamlX; |
|||
using XamlX.Ast; |
|||
using XamlX.Emit; |
|||
using XamlX.IL; |
|||
using XamlX.IL.Emitters; |
|||
using XamlX.Transform; |
|||
using XamlX.Transform.Transformers; |
|||
using XamlX.TypeSystem; |
|||
|
|||
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; |
|||
|
|||
internal class AvaloniaXamlIlOnPlatformTransformer : IXamlAstTransformer |
|||
{ |
|||
private const string OnPlatformFqn = "Avalonia.Markup.Xaml:Avalonia.Markup.Xaml.MarkupExtensions.OnPlatformExtension"; |
|||
private const string OnFqn = "Avalonia.Markup.Xaml:Avalonia.Markup.Xaml.MarkupExtensions.On"; |
|||
|
|||
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) |
|||
{ |
|||
if (node is XamlAstXamlPropertyValueNode targetPropertyNode |
|||
&& targetPropertyNode.Values.OfType<XamlMarkupExtensionNode>().FirstOrDefault() is |
|||
{ |
|||
Value: XamlAstObjectNode { Type: XamlAstClrTypeReference { Type: { } type } } objectNode |
|||
} |
|||
&& type.GetFqn().StartsWith(OnPlatformFqn)) |
|||
{ |
|||
var typeArgument = type.GenericArguments?.FirstOrDefault(); |
|||
var targetType = typeArgument ?? objectNode.Type.GetClrType(); |
|||
if (targetType is null) |
|||
{ |
|||
throw new XamlParseException( |
|||
"Unable to find OnPlatform property type. Try to set x:TypeArguments on the markup extension.", |
|||
node); |
|||
} |
|||
|
|||
IXamlAstNode defaultValue = null; |
|||
var values = new Dictionary<string, IXamlAstNode>(); |
|||
|
|||
var directives = objectNode.Children.OfType<XamlAstXmlDirective>().ToArray(); |
|||
|
|||
foreach (var child in objectNode.Arguments.Take(1)) |
|||
{ |
|||
defaultValue = new XamlAstXamlPropertyValueNode(child, targetPropertyNode.Property, child); |
|||
} |
|||
|
|||
foreach (var extProp in objectNode.Children.OfType<XamlAstXamlPropertyValueNode>()) |
|||
{ |
|||
var onObjs = extProp.Values.OfType<XamlAstObjectNode>() |
|||
.Where(o => o.Type.GetClrType().GetFqn() == OnFqn).ToArray(); |
|||
if (onObjs.Any()) |
|||
{ |
|||
foreach (var onObj in onObjs) |
|||
{ |
|||
var platformStr = (onObj.Children.OfType<XamlAstXamlPropertyValueNode>() |
|||
.SingleOrDefault(v => v.Property.GetClrProperty().Name == "Platform") |
|||
?.Values.Single() as XamlAstTextNode)? |
|||
.Text; |
|||
if (string.IsNullOrWhiteSpace(platformStr)) |
|||
{ |
|||
throw new XamlParseException("On.Platform string must be set", onObj); |
|||
} |
|||
|
|||
var content = onObj.Children.OfType<XamlAstXamlPropertyValueNode>() |
|||
.SingleOrDefault(v => v.Property.GetClrProperty().Name == "Content"); |
|||
if (content is null) |
|||
{ |
|||
throw new XamlParseException("On content object must be set", onObj); |
|||
} |
|||
|
|||
var transformed = TransformNode(targetPropertyNode.Property, content.Values, |
|||
typeArgument, directives, content); |
|||
foreach (var platform in platformStr.Split(new[] { ',' }, |
|||
StringSplitOptions.RemoveEmptyEntries)) |
|||
{ |
|||
values.Add(platform.Trim().ToUpperInvariant(), transformed); |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
|
|||
var platformStr = extProp.Property.GetClrProperty().Name.Trim().ToUpperInvariant(); |
|||
var transformed = TransformNode(targetPropertyNode.Property, extProp.Values, |
|||
typeArgument, directives, extProp); |
|||
if (platformStr.Equals("default", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
defaultValue = transformed; |
|||
} |
|||
else |
|||
{ |
|||
values.Add(platformStr, transformed); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return new XamlIlOnPlatformExtensionNode( |
|||
defaultValue, values, |
|||
new XamlAstClrTypeReference(node, targetType, false), |
|||
node); |
|||
} |
|||
|
|||
return node; |
|||
|
|||
XamlAstXamlPropertyValueNode TransformNode( |
|||
IXamlAstPropertyReference property, |
|||
IReadOnlyCollection<IXamlAstValueNode> values, |
|||
IXamlType suggestedType, |
|||
IReadOnlyCollection<XamlAstXmlDirective> directives, |
|||
IXamlLineInfo line) |
|||
{ |
|||
if (suggestedType is not null) |
|||
{ |
|||
values = values |
|||
.Select(v => XamlTransformHelpers |
|||
.TryGetCorrectlyTypedValue(context, v, suggestedType, out var converted) |
|||
? converted : v) |
|||
.ToArray(); |
|||
} |
|||
|
|||
if (directives.Any()) |
|||
{ |
|||
foreach (var value in values) |
|||
{ |
|||
if (value is XamlAstObjectNode xamlAstObjectNode) |
|||
{ |
|||
xamlAstObjectNode.Children.AddRange(directives); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return new XamlAstXamlPropertyValueNode(line, property, values); |
|||
} |
|||
} |
|||
|
|||
private sealed class XamlIlOnPlatformExtensionNode : XamlAstNode, IXamlAstValueNode, |
|||
IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>, IXamlAstManipulationNode |
|||
{ |
|||
private IXamlAstNode _defaultValue; |
|||
private readonly IXamlAstNode[] _values; |
|||
private readonly string[] _valuePlatforms; |
|||
|
|||
public XamlIlOnPlatformExtensionNode( |
|||
IXamlAstNode defaultValue, |
|||
IDictionary<string, IXamlAstNode> values, |
|||
IXamlAstTypeReference targetType, |
|||
IXamlLineInfo info) : base(info) |
|||
{ |
|||
_defaultValue = defaultValue; |
|||
_values = values.Values.ToArray(); |
|||
_valuePlatforms = values.Keys.ToArray(); |
|||
Type = targetType; |
|||
} |
|||
|
|||
public override void VisitChildren(IXamlAstVisitor visitor) |
|||
{ |
|||
_defaultValue = _defaultValue?.Visit(visitor); |
|||
VisitList(_values, visitor); |
|||
} |
|||
|
|||
public IXamlAstTypeReference Type { get; } |
|||
|
|||
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, |
|||
IXamlILEmitter codeGen) |
|||
{ |
|||
var operatingSystemClass = |
|||
context.Configuration.TypeSystem.GetType( |
|||
"Avalonia.Markup.Xaml.MarkupExtensions.OnPlatformExtensionHelper"); |
|||
var isOSPlatformMethod = operatingSystemClass |
|||
.FindMethod(m => m.IsStatic && m.Parameters.Count == 1 && m.Name == "IsOSPlatform"); |
|||
|
|||
var ret = codeGen.DefineLabel(); |
|||
|
|||
for (var index = 0; index < _valuePlatforms.Length; index++) |
|||
{ |
|||
var platform = _valuePlatforms[index]; |
|||
var propertyNode = _values[index]; |
|||
|
|||
var next = codeGen.DefineLabel(); |
|||
codeGen.Ldstr(platform); |
|||
codeGen.EmitCall(isOSPlatformMethod); |
|||
codeGen.Brfalse(next); |
|||
context.Emit(propertyNode, codeGen, null); |
|||
codeGen.Br(ret); |
|||
codeGen.MarkLabel(next); |
|||
} |
|||
|
|||
if (_defaultValue is not null) |
|||
{ |
|||
codeGen.Emit(OpCodes.Nop); |
|||
context.Emit(_defaultValue, codeGen, null); |
|||
} |
|||
else |
|||
{ |
|||
codeGen.Pop(); |
|||
} |
|||
|
|||
codeGen.MarkLabel(ret); |
|||
|
|||
return XamlILNodeEmitResult.Void(1); |
|||
} |
|||
} |
|||
} |
|||
@ -1,128 +0,0 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using XamlX.Ast; |
|||
using XamlX.Transform; |
|||
using XamlX.Transform.Transformers; |
|||
using XamlX.TypeSystem; |
|||
|
|||
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; |
|||
|
|||
internal class OnPlatformTransformer : IXamlAstTransformer |
|||
{ |
|||
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) |
|||
{ |
|||
if (node is XamlAstObjectNode xmlobj |
|||
&& xmlobj.Type is XamlAstXmlTypeReference xmlref |
|||
&& xmlref.Name.StartsWith("OnPlatform") |
|||
&& !xmlref.GenericArguments.Any()) |
|||
{ |
|||
IXamlType propertyType = null; |
|||
|
|||
if (context.ParentNodes().FirstOrDefault() is XamlAstXamlPropertyValueNode parentPropertyValueNode) |
|||
{ |
|||
var property = (XamlAstNamePropertyReference)parentPropertyValueNode.Property; |
|||
var declaringType = TypeReferenceResolver.ResolveType(context, (XamlAstXmlTypeReference)property.DeclaringType, context.StrictMode); |
|||
propertyType = declaringType.Type.GetAllProperties().First(p => p.Name == property.Name).PropertyType; |
|||
} |
|||
else if (context.ParentNodes().FirstOrDefault() is XamlAstObjectNode parentNode) |
|||
{ |
|||
var parentType = parentNode.Type is XamlAstClrTypeReference clrType |
|||
? clrType.Type |
|||
: TypeReferenceResolver.ResolveType(context, (XamlAstXmlTypeReference)parentNode.Type, context.StrictMode).Type; |
|||
var contentProperty = context.Configuration.FindContentProperty(parentType); |
|||
propertyType = contentProperty.PropertyType; |
|||
} |
|||
|
|||
if (propertyType is null) |
|||
{ |
|||
throw new InvalidOperationException("Unable to find OnPlatform property type"); |
|||
} |
|||
|
|||
xmlobj.Type = TypeReferenceResolver.ResolveType(context, xmlref.XmlNamespace, xmlref.Name, |
|||
xmlref.IsMarkupExtension, new[] { new XamlAstClrTypeReference(xmlref, propertyType, false) }, |
|||
xmlref, context.StrictMode); |
|||
} |
|||
|
|||
if (node is XamlAstNamePropertyReference xmlprop |
|||
&& xmlprop.DeclaringType is XamlAstXmlTypeReference propxmlref |
|||
&& propxmlref.Name.StartsWith("OnPlatform") |
|||
&& !propxmlref.GenericArguments.Any()) |
|||
{ |
|||
var expectedType = context.ParentNodes().OfType<XamlAstObjectNode>() |
|||
.First(n => n.Type is XamlAstClrTypeReference clrRef && clrRef.Type.Name.StartsWith("OnPlatform") |
|||
|| n.Type is XamlAstXmlTypeReference xmlRef && xmlRef.Name.StartsWith("OnPlatform")) |
|||
.Type; |
|||
xmlprop.DeclaringType = expectedType; |
|||
xmlprop.TargetType = expectedType; |
|||
} |
|||
|
|||
// if (node is XamlAstObjectNode onobj
|
|||
// && onobj.Type is XamlAstXmlTypeReference onref
|
|||
// && onref.Name == "On")
|
|||
// {
|
|||
// var platformStr = (onobj.Children.OfType<XamlAstXamlPropertyValueNode>()
|
|||
// .FirstOrDefault(v => ((XamlAstNamePropertyReference)v.Property).Name == "Platform")?.Values.Single() as XamlAstTextNode)?
|
|||
// .Text;
|
|||
// if (string.IsNullOrWhiteSpace(platformStr))
|
|||
// {
|
|||
// throw new InvalidOperationException("On.Platform string must be set");
|
|||
// }
|
|||
// var content = onobj.Children.OfType<XamlAstObjectNode>().FirstOrDefault();
|
|||
// if (content is null)
|
|||
// {
|
|||
// throw new InvalidOperationException("On content object must be set");
|
|||
// }
|
|||
//
|
|||
// var parentOnPlatformObject = context.ParentNodes().OfType<XamlAstObjectNode>()
|
|||
// .First(n => n.Type is XamlAstClrTypeReference clrRef && clrRef.Type.Name.StartsWith("OnPlatform")
|
|||
// || n.Type is XamlAstXmlTypeReference xmlRef && xmlRef.Name.StartsWith("OnPlatform"));
|
|||
// parentOnPlatformObject.Children.Remove(onobj);
|
|||
// foreach (var platform in platformStr.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
|||
// {
|
|||
// var propertyNode = new XamlAstXamlPropertyValueNode(onobj,
|
|||
// new XamlAstNamePropertyReference(onobj, parentOnPlatformObject.Type, platform.Trim(),
|
|||
// parentOnPlatformObject.Type), content);
|
|||
// parentOnPlatformObject.Children.Add(propertyNode);
|
|||
// }
|
|||
//
|
|||
// return parentOnPlatformObject.Children.Last();
|
|||
// }
|
|||
// if (node is XamlAstXamlPropertyValueNode propNode)
|
|||
// {
|
|||
// var type = (propNode.Property as XamlAstNamePropertyReference).TargetType as XamlAstXmlTypeReference;
|
|||
//
|
|||
// propNode.VisitChildren(new OnPlatformGenericTypeVisitor(type));
|
|||
//
|
|||
// return node;
|
|||
// }
|
|||
|
|||
return node; |
|||
} |
|||
|
|||
private class OnPlatformGenericTypeVisitor : IXamlAstVisitor |
|||
{ |
|||
private readonly XamlAstXmlTypeReference _type; |
|||
public OnPlatformGenericTypeVisitor(IXamlAstTypeReference type) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public IXamlAstNode Visit(IXamlAstNode node) |
|||
{ |
|||
if (node is XamlAstXmlTypeReference { Name: "OnPlatform" } xmlref) |
|||
{ |
|||
xmlref.GenericArguments.Add(_type); |
|||
} |
|||
|
|||
return node; |
|||
} |
|||
|
|||
public void Push(IXamlAstNode node) |
|||
{ |
|||
} |
|||
|
|||
public void Pop() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit 2c3b53a3db61018db620418fab509ba1d168ff24 |
|||
Subproject commit f1bfeb0c575cf05175debbd518153d2674dd90b7 |
|||
Loading…
Reference in new issue