Browse Source
Use sprache instead of Roslyn for parsing expressions. One test still failing.pull/237/head
26 changed files with 677 additions and 62 deletions
Binary file not shown.
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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, |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue