9 changed files with 423 additions and 34 deletions
@ -0,0 +1,278 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using Avalonia.Markup.Parsers; |
|||
using XamlIl; |
|||
using XamlIl.Ast; |
|||
using XamlIl.Transform; |
|||
using XamlIl.Transform.Transformers; |
|||
using XamlIl.TypeSystem; |
|||
|
|||
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers |
|||
{ |
|||
class AvaloniaXamlIlSelectorTransformer : IXamlIlAstTransformer |
|||
{ |
|||
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node) |
|||
{ |
|||
if (!(node is XamlIlAstXamlPropertyValueNode pn |
|||
&& pn.Property.GetClrProperty().PropertyType.FullName == "Avalonia.Styling.Selector")) |
|||
return node; |
|||
|
|||
if (pn.Values.Count != 1) |
|||
throw new XamlIlParseException("Selector property should should have exactly one value", node); |
|||
if (!(pn.Values[0] is XamlIlAstTextNode tn)) |
|||
return node; |
|||
|
|||
var selectorType = pn.Property.GetClrProperty().PropertyType; |
|||
|
|||
XamlIlSelectorNode Create(IEnumerable<SelectorGrammar.ISyntax> syntax, |
|||
Func<string, string, IXamlIlType> typeResolver) |
|||
{ |
|||
XamlIlSelectorNode result = new XamlIlSelectorInitialNode(node, selectorType); |
|||
|
|||
foreach (var i in syntax) |
|||
{ |
|||
switch (i) |
|||
{ |
|||
|
|||
case SelectorGrammar.OfTypeSyntax ofType: |
|||
result = new XamlIlTypeSelector(result, typeResolver(ofType.Xmlns, ofType.TypeName), true); |
|||
break; |
|||
case SelectorGrammar.IsSyntax @is: |
|||
result = new XamlIlTypeSelector(result, typeResolver(@is.Xmlns, @is.TypeName), false); |
|||
break; |
|||
case SelectorGrammar.ClassSyntax @class: |
|||
result = new XamlIlStringSelector(result, XamlIlStringSelector.Type.Class, @class.Class); |
|||
break; |
|||
case SelectorGrammar.NameSyntax name: |
|||
result = new XamlIlStringSelector(result, XamlIlStringSelector.Type.Name, name.Name); |
|||
break; |
|||
case SelectorGrammar.PropertySyntax property: |
|||
{ |
|||
var type = result?.TargetType; |
|||
|
|||
if (type == null) |
|||
throw new XamlIlParseException("Property selectors must be applied to a type.", node); |
|||
|
|||
var targetProperty = |
|||
type.GetAllProperties().FirstOrDefault(p => p.Name == property.Property); |
|||
|
|||
if (targetProperty == null) |
|||
throw new XamlIlParseException($"Cannot find '{property.Property}' on '{type}", node); |
|||
|
|||
if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context, |
|||
new XamlIlAstTextNode(node, property.Value, context.Configuration.WellKnownTypes.String), |
|||
targetProperty.PropertyType, out var typedValue)) |
|||
throw new XamlIlParseException( |
|||
$"Cannot convert '{property.Value}' to '{targetProperty.PropertyType.GetFqn()}", |
|||
node); |
|||
|
|||
result = new XamlIlPropertyEqualsSelector(result, targetProperty, typedValue); |
|||
break; |
|||
} |
|||
case SelectorGrammar.ChildSyntax child: |
|||
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.Type.Child); |
|||
break; |
|||
case SelectorGrammar.DescendantSyntax descendant: |
|||
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.Type.Descendant); |
|||
break; |
|||
case SelectorGrammar.TemplateSyntax template: |
|||
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.Type.Template); |
|||
break; |
|||
case SelectorGrammar.NotSyntax not: |
|||
result = new XamlIlNotSelector(result, Create(not.Argument, typeResolver)); |
|||
break; |
|||
default: |
|||
throw new XamlIlParseException($"Unsupported selector grammar '{i.GetType()}'.", node); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
IEnumerable<SelectorGrammar.ISyntax> parsed; |
|||
try |
|||
{ |
|||
parsed = SelectorGrammar.Parse(tn.Text); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
throw new XamlIlParseException("Unable to parse selector: " + e.Message, node); |
|||
} |
|||
|
|||
var selector = Create(parsed, (p, n) |
|||
=> XamlIlTypeReferenceResolver.ResolveType(context, $"{p}:{n}", node, true)); |
|||
pn.Values[0] = selector; |
|||
return node; |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
|
|||
abstract class XamlIlSelectorNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode |
|||
{ |
|||
public XamlIlSelectorNode Previous { get; } |
|||
public abstract IXamlIlType TargetType { get; } |
|||
|
|||
public XamlIlSelectorNode(XamlIlSelectorNode previous, |
|||
IXamlIlLineInfo info = null, |
|||
IXamlIlType selectorType = null) : base(info ?? previous) |
|||
{ |
|||
Previous = previous; |
|||
Type = selectorType == null ? previous.Type : new XamlIlAstClrTypeReference(this, selectorType); |
|||
} |
|||
|
|||
public IXamlIlAstTypeReference Type { get; } |
|||
|
|||
public virtual XamlIlNodeEmitResult Emit(XamlIlEmitContext 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(XamlIlEmitContext context, IXamlIlEmitter codeGen); |
|||
|
|||
protected void EmitCall(XamlIlEmitContext context, IXamlIlEmitter codeGen, Func<IXamlIlMethod, bool> method) |
|||
{ |
|||
var selectors = context.Configuration.TypeSystem.GetType("Avalonia.Styling.Selectors"); |
|||
var found = selectors.FindMethod(m => m.IsStatic && m.Parameters.Count > 0 && |
|||
m.Parameters[0].FullName == "Avalonia.Styling.Selector" |
|||
&& method(m)); |
|||
codeGen.EmitCall(found); |
|||
} |
|||
} |
|||
|
|||
class XamlIlSelectorInitialNode : XamlIlSelectorNode |
|||
{ |
|||
public XamlIlSelectorInitialNode(IXamlIlLineInfo info, |
|||
IXamlIlType selectorType) : base(null, info, selectorType) |
|||
{ |
|||
} |
|||
|
|||
public override IXamlIlType TargetType => null; |
|||
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen) => codeGen.Ldnull(); |
|||
} |
|||
|
|||
class XamlIlTypeSelector : XamlIlSelectorNode |
|||
{ |
|||
public bool Concrete { get; } |
|||
|
|||
public XamlIlTypeSelector(XamlIlSelectorNode previous, IXamlIlType type, bool concrete) : base(previous) |
|||
{ |
|||
TargetType = type; |
|||
Concrete = concrete; |
|||
} |
|||
|
|||
public override IXamlIlType TargetType { get; } |
|||
protected override void DoEmit(XamlIlEmitContext 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 Type |
|||
{ |
|||
Class, |
|||
Name |
|||
} |
|||
|
|||
private Type _type; |
|||
|
|||
public XamlIlStringSelector(XamlIlSelectorNode previous, Type type, string s) : base(previous) |
|||
{ |
|||
_type = type; |
|||
String = s; |
|||
} |
|||
|
|||
|
|||
public override IXamlIlType TargetType => Previous?.TargetType; |
|||
protected override void DoEmit(XamlIlEmitContext 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 Type _type; |
|||
|
|||
public enum Type |
|||
{ |
|||
Child, |
|||
Descendant, |
|||
Template |
|||
} |
|||
public XamlIlCombinatorSelector(XamlIlSelectorNode previous, Type type) : base(previous) |
|||
{ |
|||
_type = type; |
|||
} |
|||
|
|||
public override IXamlIlType TargetType => null; |
|||
protected override void DoEmit(XamlIlEmitContext 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) |
|||
{ |
|||
} |
|||
|
|||
public override IXamlIlType TargetType => Previous?.TargetType; |
|||
protected override void DoEmit(XamlIlEmitContext 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 XamlIlPropertyEqualsSelector : XamlIlSelectorNode |
|||
{ |
|||
public XamlIlPropertyEqualsSelector(XamlIlSelectorNode previous, |
|||
IXamlIlProperty property, |
|||
IXamlIlAstValueNode value) |
|||
: base(previous) |
|||
{ |
|||
Property = property; |
|||
Value = value; |
|||
} |
|||
|
|||
public IXamlIlProperty Property { get; set; } |
|||
public IXamlIlAstValueNode Value { get; set; } |
|||
|
|||
public override IXamlIlType TargetType => Previous?.TargetType; |
|||
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen) |
|||
{ |
|||
if (!AvaloniaPropertyDescriptorEmitter.Emit(context, codeGen, Property)) |
|||
throw new XamlIlLoadException( |
|||
$"{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)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,118 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Markup.Xaml.Parsers; |
|||
using Avalonia.Utilities; |
|||
using XamlIl; |
|||
using XamlIl.Ast; |
|||
using XamlIl.Transform; |
|||
using XamlIl.Transform.Transformers; |
|||
using XamlIl.TypeSystem; |
|||
|
|||
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers |
|||
{ |
|||
public class AvaloniaXamlIlSetterTransformer : IXamlIlAstTransformer |
|||
{ |
|||
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node) |
|||
{ |
|||
if (!(node is XamlIlAstObjectNode on |
|||
&& on.Type.GetClrType().FullName == "Avalonia.Styling.Setter")) |
|||
return node; |
|||
var parent = context.ParentNodes().OfType<XamlIlAstObjectNode>() |
|||
.FirstOrDefault(x => x.Type.GetClrType().FullName == "Avalonia.Styling.Style"); |
|||
if (parent == null) |
|||
throw new XamlIlParseException( |
|||
"Avalonia.Styling.Setter is only valid inside Avalonia.Styling.Style", node); |
|||
var selectorProperty = parent.Children.OfType<XamlIlAstXamlPropertyValueNode>() |
|||
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector"); |
|||
if (selectorProperty == null) |
|||
throw new XamlIlParseException( |
|||
"Can not find parent Style Selector", node); |
|||
var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode; |
|||
if (selector?.TargetType == null) |
|||
throw new XamlIlParseException( |
|||
"Can not resolve parent Style Selector type", node); |
|||
|
|||
|
|||
var property = @on.Children.OfType<XamlIlAstXamlPropertyValueNode>() |
|||
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "Property"); |
|||
if (property == null) |
|||
throw new XamlIlParseException("Setter without a property is not valid", node); |
|||
|
|||
var propertyName = property.Values.OfType<XamlIlAstTextNode>().FirstOrDefault()?.Text; |
|||
if (propertyName == null) |
|||
throw new XamlIlParseException("Setter.Property must be a string", node); |
|||
|
|||
|
|||
var selectorTypeReference = new XamlIlAstClrTypeReference(selector, selector.TargetType); |
|||
XamlIlAstNamePropertyReference forgedReference; |
|||
|
|||
var parser = new PropertyParser(); |
|||
var parsedPropertyName = parser.Parse(new CharacterReader(propertyName.AsSpan())); |
|||
if(parsedPropertyName.owner == null) |
|||
forgedReference = new XamlIlAstNamePropertyReference(property.Values[0], selectorTypeReference, |
|||
propertyName, selectorTypeReference); |
|||
else |
|||
{ |
|||
var xmlOwner = parsedPropertyName.ns; |
|||
if (xmlOwner != null) |
|||
xmlOwner += ":"; |
|||
xmlOwner += parsedPropertyName.owner; |
|||
|
|||
var t = XamlIlTypeReferenceResolver.ResolveType(context, xmlOwner, property.Values[0], true); |
|||
var tref = new XamlIlAstClrTypeReference(property.Values[0], t); |
|||
forgedReference = new XamlIlAstNamePropertyReference(property.Values[0], |
|||
tref, parsedPropertyName.name, tref); |
|||
} |
|||
|
|||
var clrProperty = |
|||
((XamlIlAstClrPropertyReference)new XamlIlPropertyReferenceResolver().Transform(context, |
|||
forgedReference)).Property; |
|||
|
|||
property.Values = new List<IXamlIlAstValueNode> |
|||
{ |
|||
new XamlIlAvaloniaPropertyNode(property.Values[0], property.Property.GetClrProperty().PropertyType, |
|||
clrProperty) |
|||
}; |
|||
|
|||
var valueProperty = on.Children |
|||
.OfType<XamlIlAstXamlPropertyValueNode>().FirstOrDefault(p => p.Property.GetClrProperty().Name == "Value"); |
|||
if (valueProperty?.Values?.Count == 1 && valueProperty.Values[0] is XamlIlAstTextNode) |
|||
{ |
|||
if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0], |
|||
clrProperty.PropertyType, out var converted)) |
|||
throw new XamlIlParseException( |
|||
$"Unable to convert property value to {clrProperty.PropertyType.GetFqn()}", |
|||
valueProperty.Values[0]); |
|||
|
|||
valueProperty.Values = new List<IXamlIlAstValueNode> |
|||
{ |
|||
new XamlIlAstRuntimeCastNode(converted, converted, |
|||
new XamlIlAstClrTypeReference(converted, context.Configuration.WellKnownTypes.Object)) |
|||
}; |
|||
} |
|||
|
|||
return node; |
|||
} |
|||
|
|||
} |
|||
|
|||
class XamlIlAvaloniaPropertyNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode |
|||
{ |
|||
public XamlIlAvaloniaPropertyNode(IXamlIlLineInfo lineInfo, IXamlIlType type, IXamlIlProperty property) : base(lineInfo) |
|||
{ |
|||
Type = new XamlIlAstClrTypeReference(this, type); |
|||
Property = property; |
|||
} |
|||
|
|||
public IXamlIlProperty Property { get; set; } |
|||
|
|||
public IXamlIlAstTypeReference Type { get; } |
|||
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen) |
|||
{ |
|||
if (!AvaloniaPropertyDescriptorEmitter.Emit(context, codeGen, Property)) |
|||
throw new XamlIlLoadException(Property.Name + " is not an AvaloniaProperty", this); |
|||
return XamlIlNodeEmitResult.Type(0, Type.GetClrType()); |
|||
} |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit 0c66a9a0a5226378ac0b2c756a85129b94cf1de9 |
|||
Subproject commit 04f47415b816692684603efda909257b0004e6b8 |
|||
Loading…
Reference in new issue