Browse Source

Use Sprache for parsing expressions.

Use sprache instead of Roslyn for parsing expressions. One test still failing.
pull/237/head
Steven Kirk 11 years ago
parent
commit
0f5af3db63
  1. BIN
      Perspex.v2.ncrunchsolution
  2. 107
      src/Markup/Perspex.Markup/Binding/ElementAccessorNode.cs
  3. 10
      src/Markup/Perspex.Markup/Binding/ExpressionNode.cs
  4. 51
      src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs
  5. 9
      src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs
  6. 16
      src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs
  7. 18
      src/Markup/Perspex.Markup/Parsers/CSharp/BracketedArgumentListSyntax.cs
  8. 17
      src/Markup/Perspex.Markup/Parsers/CSharp/CSharpSyntaxTree.cs
  9. 19
      src/Markup/Perspex.Markup/Parsers/CSharp/ElementAccessExpressionSyntax.cs
  10. 15
      src/Markup/Perspex.Markup/Parsers/CSharp/ExpressionStatementSyntax.cs
  11. 9
      src/Markup/Perspex.Markup/Parsers/CSharp/ExpressionSyntax.cs
  12. 64
      src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/ExpressionGrammar.cs
  13. 41
      src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/IdentifierGrammar.cs
  14. 20
      src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/LiteralGrammar.cs
  15. 17
      src/Markup/Perspex.Markup/Parsers/CSharp/IdentifierSyntax.cs
  16. 17
      src/Markup/Perspex.Markup/Parsers/CSharp/LiteralExpressionSyntax.cs
  17. 17
      src/Markup/Perspex.Markup/Parsers/CSharp/MemberAccessExpressionSyntax.cs
  18. 23
      src/Markup/Perspex.Markup/Parsers/CSharp/PrefixUnaryExpressionSyntax.cs
  19. 10
      src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxKind.cs
  20. 9
      src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxNode.cs
  21. 15
      src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxToken.cs
  22. 34
      src/Markup/Perspex.Markup/Perspex.Markup.csproj
  23. 6
      src/Markup/Perspex.Markup/packages.config
  24. 81
      tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs
  25. 113
      tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Indexer.cs
  26. 1
      tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj

BIN
Perspex.v2.ncrunchsolution

Binary file not shown.

107
src/Markup/Perspex.Markup/Binding/ElementAccessorNode.cs

@ -0,0 +1,107 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
namespace Perspex.Markup.Binding
{
internal class ElementAccessorNode : ExpressionNode
{
private int[] _intArgs;
public ElementAccessorNode(ExpressionNode next, IList<object> arguments)
: base(next)
{
Arguments = arguments;
var intArgs = Arguments.OfType<int>().ToArray();
if (intArgs.Length == arguments.Count)
{
_intArgs = intArgs;
}
}
public IList<object> Arguments { get; }
protected override void SubscribeAndUpdate(object target)
{
CurrentValue = GetValue(target);
var incc = target as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged += CollectionChanged;
}
}
protected override void Unsubscribe(object target)
{
var incc = target as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged -= CollectionChanged;
}
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
bool update = false;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
update = _intArgs[0] >= e.NewStartingIndex;
break;
case NotifyCollectionChangedAction.Remove:
update = _intArgs[0] >= e.OldStartingIndex;
break;
case NotifyCollectionChangedAction.Replace:
update = _intArgs[0] >= e.NewStartingIndex &&
_intArgs[0] < e.NewStartingIndex + e.NewItems.Count;
break;
case NotifyCollectionChangedAction.Move:
update = (_intArgs[0] >= e.NewStartingIndex &&
_intArgs[0] < e.NewStartingIndex + e.NewItems.Count) ||
(_intArgs[0] >= e.OldStartingIndex &&
_intArgs[0] < e.OldStartingIndex + e.OldItems.Count);
break;
case NotifyCollectionChangedAction.Reset:
update = true;
break;
}
if (update)
{
CurrentValue = GetValue(sender);
}
}
private ExpressionValue GetValue(object target)
{
var typeInfo = target.GetType().GetTypeInfo();
var list = target as IList;
if (typeInfo.IsArray && _intArgs != null)
{
return new ExpressionValue(((Array)target).GetValue(_intArgs));
}
else if (target is IList && _intArgs?.Length == 1)
{
if (_intArgs[0] < list.Count)
{
return new ExpressionValue(list[_intArgs[0]]);
}
}
return ExpressionValue.None;
}
}
}

10
src/Markup/Perspex.Markup/Binding/ExpressionNode.cs

@ -95,9 +95,13 @@ namespace Perspex.Markup.Binding
}
}
protected abstract void SubscribeAndUpdate(object target);
protected abstract void Unsubscribe(object target);
protected virtual void SubscribeAndUpdate(object target)
{
CurrentValue = new ExpressionValue(target);
}
protected virtual void Unsubscribe(object target)
{
}
}
}

51
src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs

@ -3,10 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Perspex.Markup.Parsers.CSharp;
namespace Perspex.Markup.Binding
{
@ -19,9 +16,7 @@ namespace Perspex.Markup.Binding
throw new ArgumentException("'expression' may not be empty.");
}
var syntaxTree = CSharpSyntaxTree.ParseText(expression, new CSharpParseOptions(kind: SourceCodeKind.Interactive));
var syntaxRoot = syntaxTree.GetRoot();
var syntax = syntaxRoot.ChildNodes().SingleOrDefault()?.ChildNodes()?.SingleOrDefault();
var syntax = CSharpSyntaxTree.ParseExpression(expression);
if (syntax != null)
{
@ -36,9 +31,10 @@ namespace Perspex.Markup.Binding
private static ExpressionNode Build(string expression, SyntaxNode syntax, ExpressionNode next)
{
var expressionStatement = syntax as ExpressionStatementSyntax;
var identifier = syntax as IdentifierNameSyntax;
var identifier = syntax as IdentifierSyntax;
var memberAccess = syntax as MemberAccessExpressionSyntax;
var unaryExpression = syntax as PrefixUnaryExpressionSyntax;
var elementAccess = syntax as ElementAccessExpressionSyntax;
if (expressionStatement != null)
{
@ -46,18 +42,23 @@ namespace Perspex.Markup.Binding
}
else if (identifier != null)
{
next = new PropertyAccessorNode(next, identifier.Identifier.ValueText);
next = new PropertyAccessorNode(next, identifier.Name);
}
else if (memberAccess != null)
{
next = new PropertyAccessorNode(next, memberAccess.Name.Identifier.ValueText);
next = Build(expression, memberAccess.Expression, next);
next = new PropertyAccessorNode(next, memberAccess.Member.Name);
}
else if (unaryExpression != null && unaryExpression.Kind() == SyntaxKind.LogicalNotExpression)
{
next = Build(expression, unaryExpression.Operand, next);
next = new LogicalNotNode(next);
}
else if (elementAccess != null)
{
next = Build(expression, elementAccess, next);
next = Build(expression, elementAccess.Expression, next);
}
else
{
throw new Exception($"Invalid expression: {expression}");
@ -65,5 +66,35 @@ namespace Perspex.Markup.Binding
return next;
}
private static ExpressionNode Build(string expression, ElementAccessExpressionSyntax syntax, ExpressionNode next)
{
var argList = syntax.ArgumentList as BracketedArgumentListSyntax;
if (argList != null)
{
var args = new List<object>();
foreach (var arg in argList.Arguments)
{
var literal = arg as LiteralExpressionSyntax;
if (literal != null)
{
args.Add(literal.Value);
}
else
{
throw new Exception($"Invalid expression: {expression}");
}
}
return new ElementAccessorNode(next, args);
}
else
{
throw new Exception($"Invalid expression: {expression}");
}
}
}
}

9
src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs

@ -24,15 +24,6 @@ namespace Perspex.Markup.Binding
return Next.Select(x => Negate(x)).Subscribe(observer);
}
protected override void SubscribeAndUpdate(object target)
{
CurrentValue = new ExpressionValue(target);
}
protected override void Unsubscribe(object target)
{
}
private ExpressionValue Negate(ExpressionValue v)
{
if (v.HasValue)

16
src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs

@ -65,14 +65,6 @@ namespace Perspex.Markup.Binding
CurrentValue = result;
}
private void PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == PropertyName)
{
CurrentValue = new ExpressionValue(_propertyInfo.GetValue(Target));
}
}
protected override void Unsubscribe(object target)
{
var inpc = target as INotifyPropertyChanged;
@ -82,5 +74,13 @@ namespace Perspex.Markup.Binding
inpc.PropertyChanged -= PropertyChanged;
}
}
private void PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == PropertyName)
{
CurrentValue = new ExpressionValue(_propertyInfo.GetValue(Target));
}
}
}
}

18
src/Markup/Perspex.Markup/Parsers/CSharp/BracketedArgumentListSyntax.cs

@ -0,0 +1,18 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using System.Linq;
namespace Perspex.Markup.Parsers.CSharp
{
internal class BracketedArgumentListSyntax
{
public BracketedArgumentListSyntax(IEnumerable<ExpressionSyntax> arguments)
{
Arguments = arguments.ToList();
}
public IList<ExpressionSyntax> Arguments { get; }
}
}

17
src/Markup/Perspex.Markup/Parsers/CSharp/CSharpSyntaxTree.cs

@ -0,0 +1,17 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Markup.Parsers.CSharp.Grammar;
using Sprache;
namespace Perspex.Markup.Parsers.CSharp
{
public class CSharpSyntaxTree
{
internal static ExpressionStatementSyntax ParseExpression(string expression)
{
return ExpressionGrammar.ExpressionStatement().Parse(expression);
}
}
}

19
src/Markup/Perspex.Markup/Parsers/CSharp/ElementAccessExpressionSyntax.cs

@ -0,0 +1,19 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Markup.Parsers.CSharp
{
internal class ElementAccessExpressionSyntax : ExpressionSyntax
{
public ElementAccessExpressionSyntax(
ExpressionSyntax expression,
BracketedArgumentListSyntax argumentList)
{
Expression = expression;
ArgumentList = argumentList;
}
public ExpressionSyntax Expression { get; }
public BracketedArgumentListSyntax ArgumentList { get; }
}
}

15
src/Markup/Perspex.Markup/Parsers/CSharp/ExpressionStatementSyntax.cs

@ -0,0 +1,15 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Markup.Parsers.CSharp
{
internal class ExpressionStatementSyntax : SyntaxNode
{
public ExpressionStatementSyntax(ExpressionSyntax expression)
{
Expression = expression;
}
public ExpressionSyntax Expression { get; }
}
}

9
src/Markup/Perspex.Markup/Parsers/CSharp/ExpressionSyntax.cs

@ -0,0 +1,9 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Markup.Parsers.CSharp
{
internal class ExpressionSyntax : SyntaxNode
{
}
}

64
src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/ExpressionGrammar.cs

@ -0,0 +1,64 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using System.Linq;
using Sprache;
namespace Perspex.Markup.Parsers.CSharp.Grammar
{
internal class ExpressionGrammar
{
public static Parser<ExpressionStatementSyntax> ExpressionStatement()
{
return from expression in Expression().End()
select new ExpressionStatementSyntax(expression);
}
public static Parser<ExpressionSyntax> Expression()
{
return LiteralGrammar.Literal()
.Or<ExpressionSyntax>(PrefixUnary())
.Or<ExpressionSyntax>(MemberAccess())
.Or<ExpressionSyntax>(ElementAccess())
.Or<ExpressionSyntax>(IdentifierGrammar.Identifier());
}
public static Parser<MemberAccessExpressionSyntax> MemberAccess()
{
return from identifier in IdentifierGrammar.Identifier()
from dot in Parse.Char('.')
from expression in Expression()
select new MemberAccessExpressionSyntax(expression, identifier);
}
public static Parser<ElementAccessExpressionSyntax> ElementAccess()
{
return from expression in IdentifierGrammar.Identifier()
from arguments in BracketedArgumentList('[', ']')
select new ElementAccessExpressionSyntax(expression, arguments);
}
public static Parser<BracketedArgumentListSyntax> BracketedArgumentList(
char openBracket,
char closeBracket)
{
return from open in Parse.Char(openBracket)
from arguments in Arguments()
from close in Parse.Char(closeBracket)
select new BracketedArgumentListSyntax(arguments);
}
public static Parser<IEnumerable<ExpressionSyntax>> Arguments()
{
return Expression().DelimitedBy(Parse.Char(',').Token());
}
public static Parser<PrefixUnaryExpressionSyntax> PrefixUnary()
{
return from bang in Parse.Char('!')
from operand in Expression()
select new PrefixUnaryExpressionSyntax(operand, SyntaxKind.LogicalNotExpression);
}
}
}

41
src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/IdentifierGrammar.cs

@ -0,0 +1,41 @@
using System.Globalization;
using System.Linq;
using Sprache;
namespace Perspex.Markup.Parsers.CSharp.Grammar
{
internal class IdentifierGrammar
{
private static readonly Parser<char> CombiningCharacter = Parse.Char(
c =>
{
var cat = CharUnicodeInfo.GetUnicodeCategory(c);
return cat == UnicodeCategory.NonSpacingMark ||
cat == UnicodeCategory.SpacingCombiningMark;
},
"Connecting Character");
private static readonly Parser<char> ConnectingCharacter = Parse.Char(
c => CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.ConnectorPunctuation,
"Connecting Character");
private static readonly Parser<char> FormattingCharacter = Parse.Char(
c => CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.Format,
"Connecting Character");
private static readonly Parser<char> IdentifierStart = Parse.Letter.Or(Parse.Char('_'));
private static readonly Parser<char> IdentifierChar = Parse
.LetterOrDigit
.Or(ConnectingCharacter)
.Or(CombiningCharacter)
.Or(FormattingCharacter);
public static Parser<IdentifierSyntax> Identifier()
{
return from start in IdentifierStart.Once().Text()
from @char in IdentifierChar.Many().Text()
select new IdentifierSyntax(start + @char);
}
}
}

20
src/Markup/Perspex.Markup/Parsers/CSharp/Grammar/LiteralGrammar.cs

@ -0,0 +1,20 @@
using System.Globalization;
using System.Linq;
using Sprache;
namespace Perspex.Markup.Parsers.CSharp.Grammar
{
internal class LiteralGrammar
{
public static Parser<LiteralExpressionSyntax> Literal()
{
return Integer();
}
public static Parser<LiteralExpressionSyntax> Integer()
{
return from number in Parse.Number
select new LiteralExpressionSyntax(int.Parse(number), number);
}
}
}

17
src/Markup/Perspex.Markup/Parsers/CSharp/IdentifierSyntax.cs

@ -0,0 +1,17 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Perspex.Markup.Parsers.CSharp
{
internal class IdentifierSyntax : ExpressionSyntax
{
public IdentifierSyntax(string name)
{
Name = name;
}
public String Name { get; }
}
}

17
src/Markup/Perspex.Markup/Parsers/CSharp/LiteralExpressionSyntax.cs

@ -0,0 +1,17 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Markup.Parsers.CSharp
{
internal class LiteralExpressionSyntax : ExpressionSyntax
{
public LiteralExpressionSyntax(object value, string valueText)
{
Value = value;
ValueText = valueText;
}
public object Value { get; }
public string ValueText { get; }
}
}

17
src/Markup/Perspex.Markup/Parsers/CSharp/MemberAccessExpressionSyntax.cs

@ -0,0 +1,17 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Markup.Parsers.CSharp
{
internal class MemberAccessExpressionSyntax : ExpressionSyntax
{
public MemberAccessExpressionSyntax(ExpressionSyntax expression, IdentifierSyntax member)
{
Expression = expression;
Member = member;
}
public ExpressionSyntax Expression { get; }
public IdentifierSyntax Member { get; }
}
}

23
src/Markup/Perspex.Markup/Parsers/CSharp/PrefixUnaryExpressionSyntax.cs

@ -0,0 +1,23 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Markup.Parsers.CSharp
{
internal class PrefixUnaryExpressionSyntax : ExpressionSyntax
{
private SyntaxKind _kind;
public PrefixUnaryExpressionSyntax(ExpressionSyntax operand, SyntaxKind kind)
{
Operand = operand;
_kind = kind;
}
public ExpressionSyntax Operand { get; }
public SyntaxKind Kind()
{
return _kind;
}
}
}

10
src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxKind.cs

@ -0,0 +1,10 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Markup.Parsers.CSharp
{
internal enum SyntaxKind
{
LogicalNotExpression,
}
}

9
src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxNode.cs

@ -0,0 +1,9 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Markup.Parsers.CSharp
{
internal class SyntaxNode
{
}
}

15
src/Markup/Perspex.Markup/Parsers/CSharp/SyntaxToken.cs

@ -0,0 +1,15 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Markup.Parsers.CSharp
{
internal class SyntaxToken
{
public SyntaxToken(string valueText)
{
ValueText = valueText;
}
public string ValueText { get; }
}
}

34
src/Markup/Perspex.Markup/Perspex.Markup.csproj

@ -37,22 +37,28 @@
<Compile Include="Binding\ExpressionNodeBuilder.cs" />
<Compile Include="Binding\ExpressionValue.cs" />
<Compile Include="Binding\LogicalNotNode.cs" />
<Compile Include="Binding\ElementAccessorNode.cs" />
<Compile Include="Binding\PropertyAccessorNode.cs" />
<Compile Include="Binding\ExpressionNode.cs" />
<Compile Include="Binding\ExpressionObserver.cs" />
<Compile Include="Parsers\CSharp\BracketedArgumentListSyntax.cs" />
<Compile Include="Parsers\CSharp\CSharpSyntaxTree.cs" />
<Compile Include="Parsers\CSharp\ElementAccessExpressionSyntax.cs" />
<Compile Include="Parsers\CSharp\ExpressionSyntax.cs" />
<Compile Include="Parsers\CSharp\ExpressionStatementSyntax.cs" />
<Compile Include="Parsers\CSharp\Grammar\ExpressionGrammar.cs" />
<Compile Include="Parsers\CSharp\Grammar\IdentifierGrammar.cs" />
<Compile Include="Parsers\CSharp\IdentifierSyntax.cs" />
<Compile Include="Parsers\CSharp\MemberAccessExpressionSyntax.cs" />
<Compile Include="Parsers\CSharp\PrefixUnaryExpressionSyntax.cs" />
<Compile Include="Parsers\CSharp\SyntaxKind.cs" />
<Compile Include="Parsers\CSharp\SyntaxToken.cs" />
<Compile Include="Parsers\CSharp\SyntaxNode.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.CodeAnalysis, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.CodeAnalysis.Common.1.1.0-beta1-20150812-01\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.CodeAnalysis.CSharp, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.CodeAnalysis.CSharp.1.1.0-beta1-20150812-01\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Collections.Immutable, Version=1.1.36.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
<Reference Include="Sprache, Version=2.0.0.47, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Sprache.2.0.0.47\lib\portable-net4+netcore45+win8+wp8+sl5+MonoAndroid1+MonoTouch1\Sprache.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
@ -71,18 +77,10 @@
<HintPath>..\..\..\packages\Rx-PlatformServices.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.PlatformServices.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reflection.Metadata, Version=1.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\System.Reflection.Metadata.1.1.0-alpha-00009\lib\portable-net45+win8\System.Reflection.Metadata.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0-beta1-20150812-01\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" />
<Analyzer Include="..\..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0-beta1-20150812-01\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

6
src/Markup/Perspex.Markup/packages.config

@ -1,13 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0-beta1-20150812-01" targetFramework="portable45-net45+win8" />
<package id="Microsoft.CodeAnalysis.Common" version="1.1.0-beta1-20150812-01" targetFramework="portable45-net45+win8" />
<package id="Microsoft.CodeAnalysis.CSharp" version="1.1.0-beta1-20150812-01" targetFramework="portable45-net45+win8" />
<package id="Rx-Core" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Rx-Interfaces" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Rx-Linq" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Rx-Main" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="System.Collections.Immutable" version="1.1.36" targetFramework="portable45-net45+win8" />
<package id="System.Reflection.Metadata" version="1.1.0-alpha-00009" targetFramework="portable45-net45+win8" />
<package id="Sprache" version="2.0.0.47" targetFramework="portable45-net45+win8" />
</packages>

81
tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using System.Linq;
using Perspex.Markup.Binding;
using Xunit;
@ -24,9 +25,9 @@ namespace Perspex.Markup.UnitTests.Binding
var result = ToList(ExpressionNodeBuilder.Build("Foo.Bar.Baz"));
Assert.Equal(3, result.Count);
Assert.IsType<PropertyAccessorNode>(result[0]);
Assert.IsType<PropertyAccessorNode>(result[1]);
Assert.IsType<PropertyAccessorNode>(result[2]);
AssertIsProperty(result[0], "Foo");
AssertIsProperty(result[1], "Bar");
AssertIsProperty(result[2], "Baz");
}
[Fact]
@ -36,9 +37,9 @@ namespace Perspex.Markup.UnitTests.Binding
Assert.Equal(4, result.Count);
Assert.IsType<LogicalNotNode>(result[0]);
Assert.IsType<PropertyAccessorNode>(result[1]);
Assert.IsType<PropertyAccessorNode>(result[2]);
Assert.IsType<PropertyAccessorNode>(result[3]);
AssertIsProperty(result[1], "Foo");
AssertIsProperty(result[2], "Bar");
AssertIsProperty(result[3], "Baz");
}
[Fact]
@ -49,9 +50,71 @@ namespace Perspex.Markup.UnitTests.Binding
Assert.Equal(5, result.Count);
Assert.IsType<LogicalNotNode>(result[0]);
Assert.IsType<LogicalNotNode>(result[1]);
Assert.IsType<PropertyAccessorNode>(result[2]);
Assert.IsType<PropertyAccessorNode>(result[3]);
Assert.IsType<PropertyAccessorNode>(result[4]);
AssertIsProperty(result[2], "Foo");
AssertIsProperty(result[3], "Bar");
AssertIsProperty(result[4], "Baz");
}
[Fact]
public void Should_Build_Indexed_Property()
{
var result = ToList(ExpressionNodeBuilder.Build("Foo[15]"));
Assert.Equal(2, result.Count);
AssertIsProperty(result[0], "Foo");
AssertIsIndexer(result[1], 15);
Assert.IsType<ElementAccessorNode>(result[1]);
Assert.Equal(new object[] { 15 }, ((ElementAccessorNode)result[1]).Arguments.ToArray());
}
[Fact]
public void Should_Build_Multiple_Indexed_Property()
{
var result = ToList(ExpressionNodeBuilder.Build("Foo[15,6]"));
Assert.Equal(2, result.Count);
AssertIsProperty(result[0], "Foo");
Assert.IsType<ElementAccessorNode>(result[1]);
Assert.Equal(new object[] { 15, 6 }, ((ElementAccessorNode)result[1]).Arguments.ToArray());
}
[Fact]
public void Should_Build_Multiple_Indexed_Property_With_Space()
{
var result = ToList(ExpressionNodeBuilder.Build("Foo[5, 16]"));
Assert.Equal(2, result.Count);
AssertIsProperty(result[0], "Foo");
Assert.IsType<ElementAccessorNode>(result[1]);
Assert.Equal(new object[] { 5, 16 }, ((ElementAccessorNode)result[1]).Arguments.ToArray());
}
[Fact]
public void Should_Build_Indexed_Property_In_Chain()
{
var result = ToList(ExpressionNodeBuilder.Build("Foo.Bar[5, 6].Baz"));
Assert.Equal(4, result.Count);
AssertIsProperty(result[0], "Foo");
AssertIsProperty(result[1], "Bar");
Assert.IsType<ElementAccessorNode>(result[2]);
AssertIsProperty(result[3], "Baz");
}
private void AssertIsProperty(ExpressionNode node, string name)
{
Assert.IsType<PropertyAccessorNode>(node);
var p = (PropertyAccessorNode)node;
Assert.Equal(name, p.PropertyName);
}
private void AssertIsIndexer(ExpressionNode node, params object[] args)
{
Assert.IsType<ElementAccessorNode>(node);
var e = (ElementAccessorNode)node;
Assert.Equal(e.Arguments.ToArray(), args.ToArray());
}
private List<ExpressionNode> ToList(ExpressionNode node)

113
tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Indexer.cs

@ -0,0 +1,113 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using Perspex.Markup.Binding;
using Xunit;
namespace Perspex.Markup.UnitTests.Binding
{
public class ExpressionObserverTests_Indexer
{
[Fact]
public async void Should_Get_Array_Value()
{
var data = new { Foo = new [] { "foo", "bar" } };
var target = new ExpressionObserver(data, "Foo[1]");
var result = await target.Take(1);
Assert.True(result.HasValue);
Assert.Equal("bar", result.Value);
}
[Fact]
public async void Should_Get_MultiDimensional_Array_Value()
{
var data = new { Foo = new[,] { { "foo", "bar" }, { "baz", "qux" } } };
var target = new ExpressionObserver(data, "Foo[1, 1]");
var result = await target.Take(1);
Assert.True(result.HasValue);
Assert.Equal("qux", result.Value);
}
[Fact]
public async void Should_Get_List_Value()
{
var data = new { Foo = new List<string> { "foo", "bar" } };
var target = new ExpressionObserver(data, "Foo[1]");
var result = await target.Take(1);
Assert.True(result.HasValue);
Assert.Equal("bar", result.Value);
}
[Fact]
public void Should_Track_INCC_Add()
{
var data = new { Foo = new ObservableCollection<string> { "foo", "bar" } };
var target = new ExpressionObserver(data, "Foo[2]");
var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x.Value));
data.Foo.Add("baz");
Assert.Equal(new[] { null, "baz" }, result);
}
[Fact]
public void Should_Track_INCC_Remove()
{
var data = new { Foo = new ObservableCollection<string> { "foo", "bar" } };
var target = new ExpressionObserver(data, "Foo[0]");
var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x.Value));
data.Foo.RemoveAt(0);
Assert.Equal(new[] { "foo", "bar" }, result);
}
[Fact]
public void Should_Track_INCC_Replace()
{
var data = new { Foo = new ObservableCollection<string> { "foo", "bar" } };
var target = new ExpressionObserver(data, "Foo[1]");
var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x.Value));
data.Foo[1] = "baz";
Assert.Equal(new[] { "bar", "baz" }, result);
}
[Fact]
public void Should_Track_INCC_Move()
{
var data = new { Foo = new ObservableCollection<string> { "foo", "bar" } };
var target = new ExpressionObserver(data, "Foo[1]");
var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x.Value));
data.Foo.Move(0, 1);
Assert.Equal(new[] { "bar", "foo" }, result);
}
[Fact]
public void Should_Track_INCC_Reset()
{
var data = new { Foo = new ObservableCollection<string> { "foo", "bar" } };
var target = new ExpressionObserver(data, "Foo[1]");
var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x.Value));
data.Foo.Clear();
Assert.Equal(new[] { "bar", null }, result);
}
}
}

1
tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj

@ -72,6 +72,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Binding\ExpressionObserverTests_Indexer.cs" />
<Compile Include="Binding\ExpressionObserverTests_Negation.cs" />
<Compile Include="Binding\ExpressionObserverTests_Property.cs" />
<Compile Include="Binding\ExpressionNodeBuilderTests.cs" />

Loading…
Cancel
Save