14 changed files with 496 additions and 57 deletions
@ -0,0 +1,91 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Avalonia.Data.Core |
|||
{ |
|||
public class PropertyPath |
|||
{ |
|||
public IReadOnlyList<IPropertyPathElement> Elements { get; } |
|||
|
|||
public PropertyPath(IEnumerable<IPropertyPathElement> elements) |
|||
{ |
|||
Elements = elements.ToList(); |
|||
} |
|||
} |
|||
|
|||
public class PropertyPathBuilder |
|||
{ |
|||
readonly List<IPropertyPathElement> _elements = new List<IPropertyPathElement>(); |
|||
|
|||
public PropertyPathBuilder Property(IPropertyInfo property) |
|||
{ |
|||
_elements.Add(new PropertyPropertyPathElement(property)); |
|||
return this; |
|||
} |
|||
|
|||
|
|||
public PropertyPathBuilder ChildTraversal() |
|||
{ |
|||
_elements.Add(new ChildTraversalPropertyPathElement()); |
|||
return this; |
|||
} |
|||
|
|||
public PropertyPathBuilder EnsureType(Type type) |
|||
{ |
|||
_elements.Add(new EnsureTypePropertyPathElement(type)); |
|||
return this; |
|||
} |
|||
|
|||
public PropertyPathBuilder Cast(Type type) |
|||
{ |
|||
_elements.Add(new CastTypePropertyPathElement(type)); |
|||
return this; |
|||
} |
|||
|
|||
public PropertyPath Build() |
|||
{ |
|||
return new PropertyPath(_elements); |
|||
} |
|||
} |
|||
|
|||
public interface IPropertyPathElement |
|||
{ |
|||
|
|||
} |
|||
|
|||
public class PropertyPropertyPathElement : IPropertyPathElement |
|||
{ |
|||
public IPropertyInfo Property { get; } |
|||
|
|||
public PropertyPropertyPathElement(IPropertyInfo property) |
|||
{ |
|||
Property = property; |
|||
} |
|||
} |
|||
|
|||
public class ChildTraversalPropertyPathElement : IPropertyPathElement |
|||
{ |
|||
|
|||
} |
|||
|
|||
public class EnsureTypePropertyPathElement : IPropertyPathElement |
|||
{ |
|||
public Type Type { get; } |
|||
|
|||
public EnsureTypePropertyPathElement(Type type) |
|||
{ |
|||
Type = type; |
|||
} |
|||
} |
|||
|
|||
public class CastTypePropertyPathElement : IPropertyPathElement |
|||
{ |
|||
public CastTypePropertyPathElement(Type type) |
|||
{ |
|||
Type = type; |
|||
} |
|||
|
|||
public Type Type { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,250 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
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 AvaloniaXamlIlPropertyPathTransformer : IXamlIlAstTransformer |
|||
{ |
|||
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node) |
|||
{ |
|||
if (node is XamlIlAstXamlPropertyValueNode pv |
|||
&& pv.Values.Count == 1 |
|||
&& pv.Values[0] is XamlIlAstTextNode text |
|||
&& pv.Property.GetClrProperty().Getter.ReturnType.Equals(context.GetAvaloniaTypes().PropertyPath) |
|||
) |
|||
{ |
|||
var parentScope = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>() |
|||
.FirstOrDefault(); |
|||
if(parentScope == null) |
|||
throw new XamlIlParseException("No target type scope found for property path", text); |
|||
if (parentScope.ScopeType != AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style) |
|||
throw new XamlIlParseException("PropertyPath is currently only valid for styles", pv); |
|||
|
|||
|
|||
IEnumerable<PropertyPathGrammar.ISyntax> parsed; |
|||
try |
|||
{ |
|||
parsed = PropertyPathGrammar.Parse(text.Text); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
throw new XamlIlParseException("Unable to parse PropertyPath: " + e.Message, text); |
|||
} |
|||
|
|||
var elements = new List<IXamlIlPropertyPathElementNode>(); |
|||
IXamlIlType currentType = parentScope.TargetType.GetClrType(); |
|||
|
|||
|
|||
var expectProperty = true; |
|||
var expectCast = true; |
|||
var expectTraversal = false; |
|||
var types = context.GetAvaloniaTypes(); |
|||
|
|||
IXamlIlType GetType(string ns, string name) |
|||
{ |
|||
return XamlIlTypeReferenceResolver.ResolveType(context, $"{ns}:{name}", false, |
|||
text, true).GetClrType(); |
|||
} |
|||
|
|||
void HandleProperty(string name, string typeNamespace, string typeName) |
|||
{ |
|||
if(!expectProperty || currentType == null) |
|||
throw new XamlIlParseException("Unexpected property node", text); |
|||
|
|||
var propertySearchType = |
|||
typeName != null ? GetType(typeNamespace, typeName) : currentType; |
|||
|
|||
IXamlIlPropertyPathElementNode prop = null; |
|||
var avaloniaPropertyFieldName = name + "Property"; |
|||
var avaloniaPropertyField = propertySearchType.GetAllFields().FirstOrDefault(f => |
|||
f.IsStatic && f.IsPublic && f.Name == avaloniaPropertyFieldName); |
|||
if (avaloniaPropertyField != null) |
|||
{ |
|||
prop = new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyField, |
|||
XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyField, types, text)); |
|||
} |
|||
else |
|||
{ |
|||
var clrProperty = propertySearchType.GetAllProperties().FirstOrDefault(p => p.Name == name); |
|||
prop = new XamlIClrPropertyPathElementNode(clrProperty); |
|||
} |
|||
|
|||
if (prop == null) |
|||
throw new XamlIlParseException( |
|||
$"Unable to resolve property {name} on type {propertySearchType.GetFqn()}", |
|||
text); |
|||
|
|||
currentType = prop.Type; |
|||
elements.Add(prop); |
|||
expectProperty = false; |
|||
expectTraversal = expectCast = true; |
|||
} |
|||
|
|||
foreach (var ge in parsed) |
|||
{ |
|||
if (ge is PropertyPathGrammar.ChildTraversalSyntax) |
|||
{ |
|||
if (!expectTraversal) |
|||
throw new XamlIlParseException("Unexpected child traversal .", text); |
|||
elements.Add(new XamlIlChildTraversalPropertyPathElementNode()); |
|||
expectTraversal = expectCast = false; |
|||
expectProperty = true; |
|||
} |
|||
else if (ge is PropertyPathGrammar.EnsureTypeSyntax ets) |
|||
{ |
|||
if(!expectCast) |
|||
throw new XamlIlParseException("Unexpected cast node", text); |
|||
currentType = GetType(ets.TypeNamespace, ets.TypeName); |
|||
elements.Add(new XamlIlCastPropertyPathElementNode(currentType, true)); |
|||
expectProperty = false; |
|||
expectCast = expectTraversal = true; |
|||
} |
|||
else if (ge is PropertyPathGrammar.CastTypeSyntax cts) |
|||
{ |
|||
if(!expectCast) |
|||
throw new XamlIlParseException("Unexpected cast node", text); |
|||
//TODO: Check if cast can be done
|
|||
currentType = GetType(cts.TypeNamespace, cts.TypeName); |
|||
elements.Add(new XamlIlCastPropertyPathElementNode(currentType, false)); |
|||
expectProperty = false; |
|||
expectCast = expectTraversal = true; |
|||
} |
|||
else if (ge is PropertyPathGrammar.PropertySyntax ps) |
|||
{ |
|||
HandleProperty(ps.Name, null, null); |
|||
} |
|||
else if (ge is PropertyPathGrammar.TypeQualifiedPropertySyntax tqps) |
|||
{ |
|||
HandleProperty(tqps.Name, tqps.TypeNamespace, tqps.TypeName); |
|||
} |
|||
else |
|||
throw new XamlIlParseException("Unexpected node " + ge, text); |
|||
|
|||
} |
|||
var propertyPathNode = new XamlIlPropertyPathNode(text, elements, types); |
|||
if (propertyPathNode.Type == null) |
|||
throw new XamlIlParseException("Unexpected end of the property path", text); |
|||
pv.Values[0] = propertyPathNode; |
|||
} |
|||
|
|||
return node; |
|||
} |
|||
|
|||
interface IXamlIlPropertyPathElementNode |
|||
{ |
|||
void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen); |
|||
IXamlIlType Type { get; } |
|||
} |
|||
|
|||
class XamlIlChildTraversalPropertyPathElementNode : IXamlIlPropertyPathElementNode |
|||
{ |
|||
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen) |
|||
=> codeGen.EmitCall( |
|||
context.GetAvaloniaTypes() |
|||
.PropertyPathBuilder.FindMethod(m => m.Name == "ChildTraversal")); |
|||
|
|||
public IXamlIlType Type => null; |
|||
} |
|||
|
|||
class XamlIlAvaloniaPropertyPropertyPathElementNode : IXamlIlPropertyPathElementNode |
|||
{ |
|||
private readonly IXamlIlField _field; |
|||
|
|||
public XamlIlAvaloniaPropertyPropertyPathElementNode(IXamlIlField field, IXamlIlType propertyType) |
|||
{ |
|||
_field = field; |
|||
Type = propertyType; |
|||
} |
|||
|
|||
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen) |
|||
=> codeGen |
|||
.Ldsfld(_field) |
|||
.EmitCall(context.GetAvaloniaTypes() |
|||
.PropertyPathBuilder.FindMethod(m => m.Name == "Property")); |
|||
|
|||
public IXamlIlType Type { get; } |
|||
} |
|||
|
|||
class XamlIClrPropertyPathElementNode : IXamlIlPropertyPathElementNode |
|||
{ |
|||
private readonly IXamlIlProperty _property; |
|||
|
|||
public XamlIClrPropertyPathElementNode(IXamlIlProperty property) |
|||
{ |
|||
_property = property; |
|||
} |
|||
|
|||
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen) |
|||
{ |
|||
context.Configuration.GetExtra<XamlIlClrPropertyInfoEmitter>() |
|||
.Emit(context, codeGen, _property); |
|||
|
|||
codeGen.EmitCall(context.GetAvaloniaTypes() |
|||
.PropertyPathBuilder.FindMethod(m => m.Name == "Property")); |
|||
} |
|||
|
|||
public IXamlIlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0]; |
|||
} |
|||
|
|||
class XamlIlCastPropertyPathElementNode : IXamlIlPropertyPathElementNode |
|||
{ |
|||
private readonly IXamlIlType _type; |
|||
private readonly bool _ensureType; |
|||
|
|||
public XamlIlCastPropertyPathElementNode(IXamlIlType type, bool ensureType) |
|||
{ |
|||
_type = type; |
|||
_ensureType = ensureType; |
|||
} |
|||
|
|||
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen) |
|||
{ |
|||
codeGen |
|||
.Ldtype(_type) |
|||
.EmitCall(context.GetAvaloniaTypes() |
|||
.PropertyPathBuilder.FindMethod(m => m.Name == (_ensureType ? "EnsureType" : "Cast"))); |
|||
} |
|||
|
|||
public IXamlIlType Type => _type; |
|||
} |
|||
|
|||
class XamlIlPropertyPathNode : XamlIlAstNode, IXamlIlPropertyPathNode, IXamlIlAstEmitableNode |
|||
{ |
|||
private readonly List<IXamlIlPropertyPathElementNode> _elements; |
|||
private readonly AvaloniaXamlIlWellKnownTypes _types; |
|||
|
|||
public XamlIlPropertyPathNode(IXamlIlLineInfo lineInfo, |
|||
List<IXamlIlPropertyPathElementNode> elements, |
|||
AvaloniaXamlIlWellKnownTypes types) : base(lineInfo) |
|||
{ |
|||
_elements = elements; |
|||
_types = types; |
|||
Type = new XamlIlAstClrTypeReference(this, types.PropertyPath, false); |
|||
} |
|||
|
|||
public IXamlIlAstTypeReference Type { get; } |
|||
public IXamlIlType PropertyType => _elements.LastOrDefault()?.Type; |
|||
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen) |
|||
{ |
|||
codeGen |
|||
.Newobj(_types.PropertyPathBuilder.FindConstructor()); |
|||
foreach(var e in _elements) |
|||
e.Emit(context, codeGen); |
|||
codeGen.EmitCall(_types.PropertyPathBuilder.FindMethod(m => m.Name == "Build")); |
|||
return XamlIlNodeEmitResult.Type(0, _types.PropertyPath); |
|||
} |
|||
} |
|||
} |
|||
|
|||
interface IXamlIlPropertyPathNode : IXamlIlAstValueNode |
|||
{ |
|||
IXamlIlType PropertyType { get; } |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit 3ea474b672ff63629300c36a9644caac2b74a1f2 |
|||
Subproject commit 9ae023a988d1b419e19386cfd3448d8e604bbc7e |
|||
Loading…
Reference in new issue