A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

501 lines
21 KiB

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Markup.Parsers;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
using XamlParseException = XamlX.XamlParseException;
using XamlLoadException = XamlX.XamlLoadException;
class AvaloniaXamlIlSelectorTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (!(node is XamlAstObjectNode on && on.Type.GetClrType().FullName == "Avalonia.Styling.Style"))
return node;
var pn = on.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector");
if (pn == null)
return node;
if (pn.Values.Count != 1)
throw new XamlParseException("Selector property should should have exactly one value", node);
if (pn.Values[0] is XamlIlSelectorNode)
//Deja vu. I've just been in this place before
return node;
if (!(pn.Values[0] is XamlAstTextNode tn))
throw new XamlParseException("Selector property should be a text node", node);
var selectorType = pn.Property.GetClrProperty().Getter.ReturnType;
var initialNode = new XamlIlSelectorInitialNode(node, selectorType);
var avaloniaAttachedPropertyT = context.GetAvaloniaTypes().AvaloniaAttachedPropertyT;
XamlIlSelectorNode Create(IEnumerable<SelectorGrammar.ISyntax> syntax,
Func<string, string, XamlAstClrTypeReference> typeResolver)
{
XamlIlSelectorNode result = initialNode;
XamlIlOrSelectorNode results = null;
foreach (var i in syntax)
{
switch (i)
{
case SelectorGrammar.OfTypeSyntax ofType:
result = new XamlIlTypeSelector(result, typeResolver(ofType.Xmlns, ofType.TypeName).Type, true);
break;
case SelectorGrammar.IsSyntax @is:
result = new XamlIlTypeSelector(result, typeResolver(@is.Xmlns, @is.TypeName).Type, false);
break;
case SelectorGrammar.ClassSyntax @class:
result = new XamlIlStringSelector(result, XamlIlStringSelector.SelectorType.Class, @class.Class);
break;
case SelectorGrammar.NameSyntax name:
result = new XamlIlStringSelector(result, XamlIlStringSelector.SelectorType.Name, name.Name);
break;
case SelectorGrammar.PropertySyntax property:
{
var type = result?.TargetType;
if (type == null)
throw new XamlParseException("Property selectors must be applied to a type.", node);
var targetProperty =
type.GetAllProperties().FirstOrDefault(p => p.Name == property.Property);
if (targetProperty == null)
throw new XamlParseException($"Cannot find '{property.Property}' on '{type}", node);
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
new XamlAstTextNode(node, property.Value, type: context.Configuration.WellKnownTypes.String),
targetProperty.PropertyType, out var typedValue))
throw new XamlParseException(
$"Cannot convert '{property.Value}' to '{targetProperty.PropertyType.GetFqn()}",
node);
result = new XamlIlPropertyEqualsSelector(result, targetProperty, typedValue);
break;
}
case SelectorGrammar.AttachedPropertySyntax attachedProperty:
{
var targetType = result?.TargetType;
if (targetType == null)
{
throw new XamlParseException("Attached Property selectors must be applied to a type.",node);
}
var attachedPropertyOwnerType = typeResolver(attachedProperty.Xmlns, attachedProperty.TypeName).Type;
if (attachedPropertyOwnerType is null)
{
throw new XamlParseException($"Cannot find '{attachedProperty.Xmlns}:{attachedProperty.TypeName}",node);
}
var attachedPropertyName = attachedProperty.Property + "Property";
var targetPropertyField = attachedPropertyOwnerType.GetAllFields()
.FirstOrDefault(f => f.IsStatic
&& f.IsPublic
&& f.Name == attachedPropertyName
&& f.FieldType.GenericTypeDefinition == avaloniaAttachedPropertyT
);
if (targetPropertyField is null)
{
throw new XamlParseException($"Cannot find '{attachedProperty.Property}' on '{attachedPropertyOwnerType.GetFqn()}", node);
}
var targetPropertyType = XamlIlAvaloniaPropertyHelper
.GetAvaloniaPropertyType(targetPropertyField, context.GetAvaloniaTypes(), node);
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
new XamlAstTextNode(node, attachedProperty.Value, type: context.Configuration.WellKnownTypes.String),
targetPropertyType, out var typedValue))
throw new XamlParseException(
$"Cannot convert '{attachedProperty.Value}' to '{targetPropertyType.GetFqn()}",
node);
result = new XamlIlAttacchedPropertyEqualsSelector(result, targetPropertyField, typedValue);
break;
}
case SelectorGrammar.ChildSyntax child:
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.SelectorType.Child);
break;
case SelectorGrammar.DescendantSyntax descendant:
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.SelectorType.Descendant);
break;
case SelectorGrammar.TemplateSyntax template:
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.SelectorType.Template);
break;
case SelectorGrammar.NotSyntax not:
result = new XamlIlNotSelector(result, Create(not.Argument, typeResolver));
break;
case SelectorGrammar.NthChildSyntax nth:
result = new XamlIlNthChildSelector(result, nth.Step, nth.Offset, XamlIlNthChildSelector.SelectorType.NthChild);
break;
case SelectorGrammar.NthLastChildSyntax nth:
result = new XamlIlNthChildSelector(result, nth.Step, nth.Offset, XamlIlNthChildSelector.SelectorType.NthLastChild);
break;
case SelectorGrammar.CommaSyntax comma:
if (results == null)
results = new XamlIlOrSelectorNode(node, selectorType);
results.Add(result);
result = initialNode;
break;
case SelectorGrammar.NestingSyntax:
var parentTargetType = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>().FirstOrDefault();
if (parentTargetType is null)
throw new XamlParseException($"Cannot find parent style for nested selector.", node);
result = new XamlIlNestingSelector(result, parentTargetType.TargetType.GetClrType());
break;
default:
throw new XamlParseException($"Unsupported selector grammar '{i.GetType()}'.", node);
}
}
if (results != null && result != null)
{
results.Add(result);
}
return results ?? result;
}
IEnumerable<SelectorGrammar.ISyntax> parsed;
try
{
parsed = SelectorGrammar.Parse(tn.Text);
}
catch (Exception e)
{
throw new XamlParseException("Unable to parse selector: " + e.Message, node);
}
var selector = Create(parsed, (p, n)
=> TypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true));
pn.Values[0] = selector;
return new AvaloniaXamlIlTargetTypeMetadataNode(on,
new XamlAstClrTypeReference(selector, selector.TargetType, false),
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
}
}
abstract class XamlIlSelectorNode : XamlAstNode, IXamlAstValueNode, IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
{
protected XamlIlSelectorNode Previous { get; }
public abstract IXamlType TargetType { get; }
public XamlIlSelectorNode(XamlIlSelectorNode previous,
IXamlLineInfo info = null,
IXamlType selectorType = null) : base(info ?? previous)
{
Previous = previous;
Type = selectorType == null ? previous.Type : new XamlAstClrTypeReference(this, selectorType, false);
}
public IXamlAstTypeReference Type { get; }
public virtual XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
if (Previous != null)
context.Emit(Previous, codeGen, Type.GetClrType());
DoEmit(context, codeGen);
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}
protected abstract void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen);
protected void EmitCall(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen, Func<IXamlMethod, bool> method)
{
var selectors = context.Configuration.TypeSystem.GetType("Avalonia.Styling.Selectors");
var found = selectors.FindMethod(m => m.IsStatic && m.Parameters.Count > 0 && method(m));
codeGen.EmitCall(found);
}
}
class XamlIlSelectorInitialNode : XamlIlSelectorNode
{
public XamlIlSelectorInitialNode(IXamlLineInfo info,
IXamlType selectorType) : base(null, info, selectorType)
{
}
public override IXamlType TargetType => null;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen) => codeGen.Ldnull();
}
class XamlIlTypeSelector : XamlIlSelectorNode
{
public bool Concrete { get; }
public XamlIlTypeSelector(XamlIlSelectorNode previous, IXamlType type, bool concrete) : base(previous)
{
TargetType = type;
Concrete = concrete;
}
public override IXamlType TargetType { get; }
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var name = Concrete ? "OfType" : "Is";
codeGen.Ldtype(TargetType);
EmitCall(context, codeGen,
m => m.Name == name && m.Parameters.Count == 2 && m.Parameters[1].FullName == "System.Type");
}
}
class XamlIlStringSelector : XamlIlSelectorNode
{
public string String { get; set; }
public enum SelectorType
{
Class,
Name
}
private SelectorType _type;
public XamlIlStringSelector(XamlIlSelectorNode previous, SelectorType type, string s) : base(previous)
{
_type = type;
String = s;
}
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldstr(String);
var name = _type.ToString();
EmitCall(context, codeGen,
m => m.Name == name && m.Parameters.Count == 2 && m.Parameters[1].FullName == "System.String");
}
}
class XamlIlCombinatorSelector : XamlIlSelectorNode
{
private readonly SelectorType _type;
public enum SelectorType
{
Child,
Descendant,
Template
}
public XamlIlCombinatorSelector(XamlIlSelectorNode previous, SelectorType type) : base(previous)
{
_type = type;
}
public override IXamlType TargetType => null;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var name = _type.ToString();
EmitCall(context, codeGen,
m => m.Name == name && m.Parameters.Count == 1);
}
}
class XamlIlNotSelector : XamlIlSelectorNode
{
public XamlIlSelectorNode Argument { get; }
public XamlIlNotSelector(XamlIlSelectorNode previous, XamlIlSelectorNode argument) : base(previous)
{
Argument = argument;
}
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
context.Emit(Argument, codeGen, Type.GetClrType());
EmitCall(context, codeGen,
m => m.Name == "Not" && m.Parameters.Count == 2 && m.Parameters[1].Equals(Type.GetClrType()));
}
}
class XamlIlNthChildSelector : XamlIlSelectorNode
{
private readonly int _step;
private readonly int _offset;
private readonly SelectorType _type;
public enum SelectorType
{
NthChild,
NthLastChild
}
public XamlIlNthChildSelector(XamlIlSelectorNode previous, int step, int offset, SelectorType type) : base(previous)
{
_step = step;
_offset = offset;
_type = type;
}
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldc_I4(_step);
codeGen.Ldc_I4(_offset);
EmitCall(context, codeGen,
m => m.Name == _type.ToString() && m.Parameters.Count == 3);
}
}
class XamlIlPropertyEqualsSelector : XamlIlSelectorNode
{
public XamlIlPropertyEqualsSelector(XamlIlSelectorNode previous,
IXamlProperty property,
IXamlAstValueNode value)
: base(previous)
{
Property = property;
Value = value;
}
public IXamlProperty Property { get; set; }
public IXamlAstValueNode Value { get; set; }
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
if (!XamlIlAvaloniaPropertyHelper.Emit(context, codeGen, Property))
throw new XamlLoadException(
$"{Property.Name} of {(Property.Setter ?? Property.Getter).DeclaringType.GetFqn()} doesn't seem to be an AvaloniaProperty",
this);
context.Emit(Value, codeGen, context.Configuration.WellKnownTypes.Object);
EmitCall(context, codeGen,
m => m.Name == "PropertyEquals"
&& m.Parameters.Count == 3
&& m.Parameters[1].FullName == "Avalonia.AvaloniaProperty"
&& m.Parameters[2].Equals(context.Configuration.WellKnownTypes.Object));
}
}
class XamlIlAttacchedPropertyEqualsSelector : XamlIlSelectorNode
{
public XamlIlAttacchedPropertyEqualsSelector(XamlIlSelectorNode previous,
IXamlField propertyFiled,
IXamlAstValueNode value)
: base(previous)
{
PropertyFiled = propertyFiled;
Value = value;
}
public IXamlField PropertyFiled { get; set; }
public IXamlAstValueNode Value { get; set; }
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldsfld(PropertyFiled);
context.Emit(Value, codeGen, context.Configuration.WellKnownTypes.Object);
EmitCall(context, codeGen,
m => m.Name == "PropertyEquals"
&& m.Parameters.Count == 3
&& m.Parameters[1].FullName == "Avalonia.AvaloniaProperty"
&& m.Parameters[2].Equals(context.Configuration.WellKnownTypes.Object));
}
}
class XamlIlOrSelectorNode : XamlIlSelectorNode
{
List<XamlIlSelectorNode> _selectors = new List<XamlIlSelectorNode>();
public XamlIlOrSelectorNode(IXamlLineInfo info, IXamlType selectorType) : base(null, info, selectorType)
{
}
public void Add(XamlIlSelectorNode node)
{
_selectors.Add(node);
}
public override IXamlType TargetType
{
get
{
IXamlType result = null;
foreach (var selector in _selectors)
{
if (selector.TargetType == null)
{
return null;
}
else if (result == null)
{
result = selector.TargetType;
}
else
{
while (!result.IsAssignableFrom(selector.TargetType))
{
result = result.BaseType;
}
}
}
return result;
}
}
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
if (_selectors.Count == 0)
throw new XamlLoadException("Invalid selector count", this);
if (_selectors.Count == 1)
{
_selectors[0].Emit(context, codeGen);
return;
}
var listType = context.Configuration.TypeSystem.FindType("System.Collections.Generic.List`1")
.MakeGenericType(base.Type.GetClrType());
var add = listType.FindMethod("Add", context.Configuration.WellKnownTypes.Void, false, Type.GetClrType());
codeGen
.Newobj(listType.FindConstructor());
foreach (var s in _selectors)
{
codeGen.Dup();
context.Emit(s, codeGen, Type.GetClrType());
codeGen.EmitCall(add, true);
}
EmitCall(context, codeGen,
m => m.Name == "Or" && m.Parameters.Count == 1 && m.Parameters[0].Name.StartsWith("IReadOnlyList"));
}
}
class XamlIlNestingSelector : XamlIlSelectorNode
{
public XamlIlNestingSelector(XamlIlSelectorNode previous, IXamlType targetType)
: base(previous)
{
TargetType = targetType;
}
public override IXamlType TargetType { get; }
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
EmitCall(context, codeGen,
m => m.Name == "Nesting" && m.Parameters.Count == 1);
}
}
}