├── .gitignore ├── QuickConverterKey.snk ├── Tokens ├── IPostToken.cs ├── PostTokenChainToken.cs ├── StaticMemberToken.cs ├── ThrowToken.cs ├── TypeofToken.cs ├── ParameterToken.cs ├── AsToken.cs ├── IsToken.cs ├── BracketedToken.cs ├── InstanceMemberToken.cs ├── NullPropagatingToken.cs ├── UnaryOperatorToken.cs ├── LambdaAssignmentToken.cs ├── NullCoalesceOperatorToken.cs ├── TypeCastToken.cs ├── IndexerToken.cs ├── TernaryOperatorToken.cs ├── BinaryOperatorToken.cs ├── StaticFunctionToken.cs ├── ArgumentListToken.cs ├── AssignmentToken.cs ├── ConstantToken.cs ├── LambdaToken.cs ├── TokenBase.cs ├── InstanceFunctionToken.cs └── ConstructorToken.cs ├── ConverterParameterMode.cs ├── QuickConverterEventType.cs ├── DataContainer.cs ├── TokenizationFailureEventArgs.cs ├── QuickConverterEventArgs.cs ├── TokenizationSuccessEventArgs.cs ├── Operator.cs ├── MarkupExtensionExceptionEventArgs.cs ├── QuickConverter.nuspec ├── LICENSE ├── QuickConverter.sln ├── ChainedConverterExceptionEventArgs.cs ├── RuntimeSingleConvertExceptionEventArgs.cs ├── Properties └── AssemblyInfo.cs ├── RuntimeEventHandlerExceptionEventArgs.cs ├── .gitattributes ├── RuntimeMultiConvertExceptionEventArgs.cs ├── QuickValue.cs ├── Binding.cs ├── QuickEventHandler.cs ├── QuickConverter.csproj ├── DynamicSingleConverter.cs ├── DynamicMultiConverter.cs ├── QuickEvent.cs ├── README.md ├── QuickConverter.cs ├── QuickMultiConverter.cs ├── MultiBinding.cs └── EquationTokenizer.cs /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | *.nupkg 4 | *.suo 5 | -------------------------------------------------------------------------------- /QuickConverterKey.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohannesMoersch/QuickConverter/HEAD/QuickConverterKey.snk -------------------------------------------------------------------------------- /Tokens/IPostToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace QuickConverter.Tokens 7 | { 8 | public interface IPostToken 9 | { 10 | TokenBase Target { get; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ConverterParameterMode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace QuickConverter 7 | { 8 | public enum ConverterParameterMode 9 | { 10 | Parameter, 11 | Values 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /QuickConverterEventType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace QuickConverter 7 | { 8 | public enum QuickConverterEventType 9 | { 10 | TokenizationSuccess, 11 | TokenizationFailure, 12 | RuntimeCodeException, 13 | MarkupException, 14 | ChainedConverterException 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DataContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | 7 | namespace QuickConverter 8 | { 9 | public class DataContainer 10 | { 11 | private ThreadLocal _value = new ThreadLocal(); 12 | public object Value 13 | { 14 | get { return _value.Value; } 15 | set { _value.Value = value; } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TokenizationFailureEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace QuickConverter 7 | { 8 | public class TokenizationFailureEventArgs : QuickConverterEventArgs 9 | { 10 | public override QuickConverterEventType Type { get { return QuickConverterEventType.TokenizationFailure; } } 11 | 12 | internal TokenizationFailureEventArgs(string expression) 13 | : base(expression) 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /QuickConverterEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace QuickConverter 7 | { 8 | public delegate void QuickConverterEventHandler(QuickConverterEventArgs args); 9 | 10 | public abstract class QuickConverterEventArgs 11 | { 12 | public abstract QuickConverterEventType Type { get; } 13 | 14 | public string Expression { get; private set; } 15 | 16 | internal QuickConverterEventArgs(string expression) 17 | { 18 | Expression = expression; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TokenizationSuccessEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using QuickConverter.Tokens; 6 | 7 | namespace QuickConverter 8 | { 9 | public class TokenizationSuccessEventArgs : QuickConverterEventArgs 10 | { 11 | public override QuickConverterEventType Type { get { return QuickConverterEventType.TokenizationSuccess; } } 12 | 13 | public TokenBase Root { get; set; } 14 | 15 | internal TokenizationSuccessEventArgs(string expression, TokenBase root) 16 | : base(expression) 17 | { 18 | Root = root; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Operator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace QuickConverter 7 | { 8 | public enum Operator 9 | { 10 | Positive = 0, 11 | Negative = 1, 12 | Not = 2, 13 | Multiply = 3, 14 | Divide = 4, 15 | Modulus = 5, 16 | Add = 6, 17 | Subtract = 7, 18 | GreaterOrEqual = 8, 19 | LessOrEqual = 9, 20 | GreaterThan = 10, 21 | LessThan = 11, 22 | Equals = 12, 23 | NotEquals = 13, 24 | And = 14, 25 | AlternateAnd = 15, 26 | Or = 16, 27 | BitwiseAnd = 17, 28 | BitwiseAlternateAnd = 18, 29 | BitwiseOr = 19, 30 | BitwiseXor = 20, 31 | Ternary = 21 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MarkupExtensionExceptionEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Windows.Markup; 6 | 7 | namespace QuickConverter 8 | { 9 | public class MarkupExtensionExceptionEventArgs : QuickConverterEventArgs 10 | { 11 | public override QuickConverterEventType Type { get { return QuickConverterEventType.MarkupException; } } 12 | 13 | public MarkupExtension MarkupExtension { get; private set; } 14 | 15 | public Exception Exception { get; private set; } 16 | 17 | internal MarkupExtensionExceptionEventArgs(string expression, MarkupExtension markupExtension, Exception exception) 18 | : base(expression) 19 | { 20 | MarkupExtension = markupExtension; 21 | Exception = exception; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /QuickConverter.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QuickConverter 5 | QuickConverter 6 | $version$ 7 | Johannes Moersch 8 | iQmetrix 9 | https://github.com/JohannesMoersch/QuickConverter/blob/master/LICENSE 10 | https://github.com/JohannesMoersch/QuickConverter 11 | false 12 | QuickConverter provides you with WPF markup that allows you to write inline converters and multi-bindings using a C# like language directly in your xaml. 13 | Johannes Moersch © 2017 14 | WPF xaml Binding converter 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 JohannesMoersch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Tokens/PostTokenChainToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | 7 | namespace QuickConverter.Tokens 8 | { 9 | public class PostTokenChainToken : TokenBase, IPostToken 10 | { 11 | internal PostTokenChainToken(TokenBase token) 12 | { 13 | Target = token; 14 | } 15 | 16 | public override Type ReturnType { get { return Target.ReturnType; } } 17 | 18 | public override TokenBase[] Children { get { return new[] { Target }; } } 19 | 20 | public TokenBase Target { get; private set; } 21 | 22 | internal override bool SetPostTarget(TokenBase target) 23 | { 24 | Target = target; 25 | return true; 26 | } 27 | 28 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 29 | { 30 | token = null; 31 | return true; 32 | } 33 | 34 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 35 | { 36 | label = Expression.Label(requiresReturnValue ? typeof(object) : typeof(void)); 37 | return Expression.Label(label, Target.GetExpression(parameters, locals, dataContainers, dynamicContext, label, requiresReturnValue)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /QuickConverter.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickConverter", "QuickConverter.csproj", "{42D0A4C4-B3B6-491B-B175-9FC5B24DE873}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x86 = Debug|x86 12 | Release|Any CPU = Release|Any CPU 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {42D0A4C4-B3B6-491B-B175-9FC5B24DE873}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {42D0A4C4-B3B6-491B-B175-9FC5B24DE873}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {42D0A4C4-B3B6-491B-B175-9FC5B24DE873}.Debug|x86.ActiveCfg = Debug|x86 19 | {42D0A4C4-B3B6-491B-B175-9FC5B24DE873}.Debug|x86.Build.0 = Debug|x86 20 | {42D0A4C4-B3B6-491B-B175-9FC5B24DE873}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {42D0A4C4-B3B6-491B-B175-9FC5B24DE873}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {42D0A4C4-B3B6-491B-B175-9FC5B24DE873}.Release|x86.ActiveCfg = Release|x86 23 | {42D0A4C4-B3B6-491B-B175-9FC5B24DE873}.Release|x86.Build.0 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /Tokens/StaticMemberToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | 8 | namespace QuickConverter.Tokens 9 | { 10 | public class StaticMemberToken : TokenBase 11 | { 12 | internal StaticMemberToken() 13 | { 14 | } 15 | 16 | public override Type ReturnType { get { return Member is FieldInfo ? (Member as FieldInfo).FieldType : (Member as PropertyInfo).PropertyType; } } 17 | 18 | public override TokenBase[] Children { get { return new TokenBase[0]; } } 19 | 20 | public MemberInfo Member { get; private set; } 21 | 22 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 23 | { 24 | token = null; 25 | var tuple = GetNameMatches(text, null, null).Where(tup => tup.Item1 is FieldInfo || tup.Item1 is PropertyInfo).Reverse().FirstOrDefault(); 26 | if (tuple == null) 27 | return false; 28 | text = tuple.Item2; 29 | token = new StaticMemberToken() { Member = tuple.Item1 as MemberInfo }; 30 | return true; 31 | } 32 | 33 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 34 | { 35 | return Expression.Convert(Expression.MakeMemberAccess(null, Member), typeof(object)); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ChainedConverterExceptionEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Windows.Data; 7 | 8 | namespace QuickConverter 9 | { 10 | public class ChainedConverterExceptionEventArgs : QuickConverterEventArgs 11 | { 12 | public override QuickConverterEventType Type { get { return QuickConverterEventType.ChainedConverterException; } } 13 | 14 | public IValueConverter ChainedConverter { get; private set; } 15 | 16 | public object InputValue { get; private set; } 17 | 18 | public Type TargetType { get; private set; } 19 | 20 | public object Parameter { get; private set; } 21 | 22 | public CultureInfo Culture { get; private set; } 23 | 24 | public bool ConvertingBack { get; private set; } 25 | 26 | public object ParentConverter { get; private set; } 27 | 28 | public Exception Exception { get; private set; } 29 | 30 | internal ChainedConverterExceptionEventArgs(string expression, object inputValue, Type targetType, object parameter, CultureInfo culture, bool convertingBack, IValueConverter chainedConverter, object parentConverter, Exception exception) 31 | : base(expression) 32 | { 33 | InputValue = inputValue; 34 | TargetType = targetType; 35 | Parameter = parameter; 36 | Culture = culture; 37 | ConvertingBack = convertingBack; 38 | ChainedConverter = chainedConverter; 39 | ParentConverter = parentConverter; 40 | Exception = exception; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tokens/ThrowToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using Microsoft.CSharp.RuntimeBinder; 8 | 9 | namespace QuickConverter.Tokens 10 | { 11 | public class ThrowToken : TokenBase 12 | { 13 | internal ThrowToken() 14 | { 15 | } 16 | 17 | public override Type ReturnType { get { return typeof(object); } } 18 | 19 | public override TokenBase[] Children { get { return new[] { Exception }; } } 20 | 21 | public TokenBase Exception { get; private set; } 22 | 23 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 24 | { 25 | token = null; 26 | if (!text.StartsWith("throw")) 27 | return false; 28 | string temp = text.Substring(5).TrimStart(); 29 | 30 | TokenBase valToken = null; 31 | if (!EquationTokenizer.TryGetValueToken(ref temp, out valToken)) 32 | return false; 33 | 34 | text = temp; 35 | token = new ThrowToken() { Exception = valToken }; 36 | return true; 37 | } 38 | 39 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 40 | { 41 | return Expression.Throw(Exception.GetExpression(parameters, locals, dataContainers, dynamicContext, label), typeof(object)); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Tokens/TypeofToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using Microsoft.CSharp.RuntimeBinder; 8 | 9 | namespace QuickConverter.Tokens 10 | { 11 | public class TypeofToken : TokenBase 12 | { 13 | internal TypeofToken() 14 | { 15 | } 16 | 17 | public override Type ReturnType { get { return typeof(Type); } } 18 | 19 | public override TokenBase[] Children { get { return new TokenBase[0]; } } 20 | 21 | public Type Type { get; private set; } 22 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 23 | { 24 | token = null; 25 | if (!text.StartsWith("typeof")) 26 | return false; 27 | string temp = text.Substring(6).TrimStart(); 28 | if (temp.Length < 3 || temp[0] != '(') 29 | return false; 30 | var name = GetNameMatches(temp.Substring(1), null, null).FirstOrDefault(tuple => tuple.Item1 is Type && tuple.Item2.TrimStart().StartsWith(")")); 31 | if (name == null) 32 | return false; 33 | text = name.Item2.TrimStart().Substring(1); 34 | token = new TypeofToken() { Type = name.Item1 as Type }; 35 | return true; 36 | } 37 | 38 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 39 | { 40 | return Expression.Constant(Type, typeof(object)); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tokens/ParameterToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | 7 | namespace QuickConverter.Tokens 8 | { 9 | public class ParameterToken : TokenBase 10 | { 11 | internal ParameterToken() 12 | { 13 | } 14 | 15 | public override Type ReturnType { get { return typeof(object); } } 16 | 17 | public override TokenBase[] Children { get { return new TokenBase[0]; } } 18 | 19 | public string Name { get; private set; } 20 | 21 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 22 | { 23 | token = null; 24 | if (text.Length < 2 || text[0] != '$' || (!Char.IsLetter(text[1]) && text[1] != '_')) 25 | return false; 26 | int count = 2; 27 | while (count < text.Length && (Char.IsLetterOrDigit(text[count]) || text[count] == '_')) 28 | ++count; 29 | token = new ParameterToken() { Name = text.Substring(1, count - 1) }; 30 | text = text.Substring(count); 31 | return true; 32 | } 33 | 34 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 35 | { 36 | if (locals.ContainsKey(Name)) 37 | return Expression.Property(locals[Name], "Value"); 38 | ParameterExpression par = parameters.FirstOrDefault(p => p.Name == Name); 39 | if (par == null) 40 | { 41 | par = Expression.Parameter(typeof(object), Name); 42 | parameters.Add(par); 43 | } 44 | return par; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Tokens/AsToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | 7 | namespace QuickConverter.Tokens 8 | { 9 | public class AsToken : TokenBase, IPostToken 10 | { 11 | internal AsToken() 12 | { 13 | } 14 | 15 | public override Type ReturnType { get { return Type; } } 16 | 17 | public override TokenBase[] Children { get { return new[] { Target }; } } 18 | 19 | public TokenBase Target { get; private set; } 20 | 21 | public Type Type { get; private set; } 22 | 23 | internal override bool SetPostTarget(TokenBase target) 24 | { 25 | Target = target; 26 | return true; 27 | } 28 | 29 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 30 | { 31 | token = null; 32 | string temp = text.TrimStart(); 33 | if (!temp.StartsWith("as")) 34 | return false; 35 | temp = temp.Substring(2).TrimStart(); 36 | var name = GetNameMatches(temp, null, null).Reverse().FirstOrDefault(tuple => tuple.Item1 is Type); 37 | if (name == null || (name.Item2.Length != 0 && name.Item2[0] == '.')) 38 | return false; 39 | text = name.Item2.TrimStart(); 40 | token = new AsToken() { Type = name.Item1 as Type }; 41 | return true; 42 | } 43 | 44 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 45 | { 46 | return Expression.Convert(Expression.TypeAs(Target.GetExpression(parameters, locals, dataContainers, dynamicContext, label), Type), typeof(object)); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tokens/IsToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | 7 | namespace QuickConverter.Tokens 8 | { 9 | public class IsToken : TokenBase, IPostToken 10 | { 11 | internal IsToken() 12 | { 13 | } 14 | 15 | public override Type ReturnType { get { return typeof(bool); } } 16 | 17 | public override TokenBase[] Children { get { return new[] { Target }; } } 18 | 19 | public TokenBase Target { get; private set; } 20 | public Type Type { get; private set; } 21 | 22 | internal override bool SetPostTarget(TokenBase target) 23 | { 24 | Target = target; 25 | return true; 26 | } 27 | 28 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 29 | { 30 | token = null; 31 | string temp = text.TrimStart(); 32 | if (!temp.StartsWith("is")) 33 | return false; 34 | temp = temp.Substring(2).TrimStart(); 35 | var name = GetNameMatches(temp, null, null).Reverse().FirstOrDefault(tuple => tuple.Item1 is Type); 36 | if (name == null || (name.Item2.Length != 0 && name.Item2[0] == '.')) 37 | return false; 38 | text = name.Item2.TrimStart(); 39 | token = new IsToken() { Type = name.Item1 as Type }; 40 | return true; 41 | } 42 | 43 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 44 | { 45 | return Expression.Convert(Expression.TypeIs(Target.GetExpression(parameters, locals, dataContainers, dynamicContext, label), Type), typeof(object)); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /RuntimeSingleConvertExceptionEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace QuickConverter 7 | { 8 | public class RuntimeSingleConvertExceptionEventArgs : QuickConverterEventArgs 9 | { 10 | public override QuickConverterEventType Type { get { return QuickConverterEventType.RuntimeCodeException; } } 11 | 12 | public object P { get; private set; } 13 | 14 | public object V0 { get; private set; } 15 | public object V1 { get; private set; } 16 | public object V2 { get; private set; } 17 | public object V3 { get; private set; } 18 | public object V4 { get; private set; } 19 | public object V5 { get; private set; } 20 | public object V6 { get; private set; } 21 | public object V7 { get; private set; } 22 | public object V8 { get; private set; } 23 | public object V9 { get; private set; } 24 | 25 | public object Value { get; private set; } 26 | 27 | public object Parameter { get; private set; } 28 | 29 | public DynamicSingleConverter Converter { get; private set; } 30 | 31 | public Exception Exception { get; private set; } 32 | 33 | public string DebugView { get; private set; } 34 | 35 | internal RuntimeSingleConvertExceptionEventArgs(string expression, string debugView, object p, object value, object[] values, object parameter, DynamicSingleConverter converter, Exception exception) 36 | : base(expression) 37 | { 38 | DebugView = debugView; 39 | P = p; 40 | V0 = values[0]; 41 | V1 = values[1]; 42 | V2 = values[2]; 43 | V3 = values[3]; 44 | V4 = values[4]; 45 | V5 = values[5]; 46 | V6 = values[6]; 47 | V7 = values[7]; 48 | V8 = values[8]; 49 | V9 = values[9]; 50 | Value = value; 51 | Parameter = parameter; 52 | Converter = converter; 53 | Exception = exception; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tokens/BracketedToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | 7 | namespace QuickConverter.Tokens 8 | { 9 | public class BracketedToken : TokenBase 10 | { 11 | internal BracketedToken() 12 | { 13 | } 14 | 15 | public override Type ReturnType { get { return Value.ReturnType; } } 16 | 17 | public override TokenBase[] Children { get { return new[] { Value }; } } 18 | 19 | public TokenBase Value { get; private set; } 20 | 21 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 22 | { 23 | token = null; 24 | if (!text.TrimStart().StartsWith("(")) 25 | return false; 26 | bool inQuotes = false; 27 | int brackets = 0; 28 | int i = 0; 29 | while (true) 30 | { 31 | if (i >= text.Length) 32 | return false; 33 | if (i > 0 && text[i] == '\'' && text[i - 1] != '\\') 34 | inQuotes = !inQuotes; 35 | else if (!inQuotes) 36 | { 37 | if (text[i] == '(') 38 | ++brackets; 39 | else if (text[i] == ')') 40 | { 41 | --brackets; 42 | if (brackets == 0) 43 | break; 44 | } 45 | } 46 | ++i; 47 | } 48 | TokenBase valToken; 49 | if (!EquationTokenizer.TryEvaluateExpression(text.Substring(1, i - 1), out valToken)) 50 | return false; 51 | text = text.Substring(i + 1); 52 | token = new BracketedToken() { Value = valToken }; 53 | return true; 54 | } 55 | 56 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 57 | { 58 | return Value.GetExpression(parameters, locals, dataContainers, dynamicContext, label); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using System.Windows.Markup; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("QuickConverter")] 10 | [assembly: AssemblyDescription("QuickConverter provides you with WPF markup that allows you to write inline converters and multi-bindings using a C# like language directly in your xaml.")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("iQmetrix")] 13 | [assembly: AssemblyProduct("QuickConverter")] 14 | [assembly: AssemblyCopyright("Johannes Moersch © 2017")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | 18 | // Setting ComVisible to false makes the types in this assembly not visible 19 | // to COM components. If you need to access a type in this assembly from 20 | // COM, set the ComVisible attribute to true on that type. 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | [assembly: Guid("be38e4cf-700f-4894-b45a-79fc17c74a61")] 25 | 26 | // Version information for an assembly consists of the following four values: 27 | // 28 | // Major Version 29 | // Minor Version 30 | // Build Number 31 | // Revision 32 | // 33 | // You can specify all the values or you can default the Build and Revision Numbers 34 | // by using the '*' as shown below: 35 | // [assembly: AssemblyVersion("1.0.*")] 36 | [assembly: AssemblyVersion("1.2.5.0")] 37 | [assembly: AssemblyFileVersion("1.2.5.0")] 38 | 39 | [assembly: XmlnsDefinition("http://QuickConverter.CodePlex.com/", "QuickConverter")] 40 | [assembly: XmlnsPrefix("http://QuickConverter.CodePlex.com/", "qc")] -------------------------------------------------------------------------------- /Tokens/InstanceMemberToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using Microsoft.CSharp.RuntimeBinder; 8 | 9 | namespace QuickConverter.Tokens 10 | { 11 | public class InstanceMemberToken : TokenBase, IPostToken 12 | { 13 | internal InstanceMemberToken() 14 | { 15 | } 16 | 17 | public override Type ReturnType { get { return typeof(object); } } 18 | 19 | public override TokenBase[] Children { get { return new[] { Target }; } } 20 | 21 | public string MemberName { get; private set; } 22 | public TokenBase Target { get; private set; } 23 | 24 | internal override bool SetPostTarget(TokenBase target) 25 | { 26 | Target = target; 27 | return true; 28 | } 29 | 30 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 31 | { 32 | token = null; 33 | string temp = text; 34 | if (temp.Length < 2 || temp[0] != '.' || (!Char.IsLetter(temp[1]) && temp[1] != '_')) 35 | return false; 36 | int count = 2; 37 | while (count < temp.Length && (Char.IsLetterOrDigit(temp[count]) || temp[count] == '_')) 38 | ++count; 39 | if (count < temp.Length && temp[count] == '(') 40 | return false; 41 | string name = temp.Substring(1, count - 1); 42 | text = temp.Substring(count); 43 | token = new InstanceMemberToken() { MemberName = name }; 44 | return true; 45 | } 46 | 47 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 48 | { 49 | CallSiteBinder binder = Binder.GetMember(CSharpBinderFlags.None, MemberName, dynamicContext ?? typeof(object), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); 50 | return Expression.Dynamic(binder, typeof(object), Target.GetExpression(parameters, locals, dataContainers, dynamicContext, label)); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Tokens/NullPropagatingToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Runtime.CompilerServices; 7 | using System.Text; 8 | using Microsoft.CSharp.RuntimeBinder; 9 | using Binder = Microsoft.CSharp.RuntimeBinder.Binder; 10 | 11 | namespace QuickConverter.Tokens 12 | { 13 | public class NullPropagatingToken : TokenBase, IPostToken 14 | { 15 | internal NullPropagatingToken() 16 | { 17 | } 18 | 19 | public override Type ReturnType { get { return Target.ReturnType; } } 20 | 21 | public override TokenBase[] Children { get { return new[] { Target }; } } 22 | 23 | public TokenBase Target { get; private set; } 24 | 25 | internal override bool SetPostTarget(TokenBase target) 26 | { 27 | Target = target; 28 | return true; 29 | } 30 | 31 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 32 | { 33 | token = null; 34 | string temp = text; 35 | if (temp.Length < 2 || temp[0] != '?' || (temp[1] != '.' && temp[1] != '[')) 36 | return false; 37 | text = temp.Substring(1); 38 | token = new NullPropagatingToken(); 39 | return true; 40 | } 41 | 42 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 43 | { 44 | var container = new DataContainer(); 45 | var constant = Expression.Constant(container); 46 | dataContainers.Add(container); 47 | 48 | return Expression.Block(new Expression[] 49 | { 50 | Expression.Assign(Expression.Property(constant, "Value"), Target.GetExpression(parameters, locals, dataContainers, dynamicContext, label)), 51 | Expression.IfThen(Expression.Equal(Expression.Property(constant, "Value"), Expression.Default(typeof(object))), Expression.Goto(label, Expression.Default(typeof(object)))), 52 | Expression.Property(constant, "Value") 53 | }); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tokens/UnaryOperatorToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using Microsoft.CSharp.RuntimeBinder; 8 | 9 | namespace QuickConverter.Tokens 10 | { 11 | public class UnaryOperatorToken : TokenBase 12 | { 13 | internal UnaryOperatorToken() 14 | { 15 | } 16 | 17 | public override Type ReturnType { get { return typeof(object); } } 18 | 19 | public override TokenBase[] Children { get { return new[] { value }; } } 20 | 21 | private Operator operation; 22 | private TokenBase value; 23 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 24 | { 25 | token = null; 26 | if (text.Length == 0) 27 | return false; 28 | Operator op; 29 | if (text[0] == '!') 30 | op = Operator.Not; 31 | else if (text[0] == '+') 32 | op = Operator.Positive; 33 | else if (text[0] == '-') 34 | op = Operator.Negative; 35 | else 36 | return false; 37 | TokenBase valToken; 38 | string temp = text.Substring(1).TrimStart(); 39 | if (!EquationTokenizer.TryGetValueToken(ref temp, out valToken)) 40 | return false; 41 | token = new UnaryOperatorToken() { operation = op, value = valToken }; 42 | text = temp; 43 | return true; 44 | } 45 | 46 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 47 | { 48 | ExpressionType type = default(ExpressionType); 49 | switch (operation) 50 | { 51 | case Operator.Positive: 52 | return value.GetExpression(parameters, locals, dataContainers, dynamicContext, label); 53 | case Operator.Negative: 54 | type = ExpressionType.Negate; 55 | break; 56 | case Operator.Not: 57 | type = ExpressionType.Not; 58 | break; 59 | } 60 | CallSiteBinder binder = Binder.UnaryOperation(CSharpBinderFlags.None, type, dynamicContext ?? typeof(object), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); 61 | return Expression.Dynamic(binder, typeof(object), value.GetExpression(parameters, locals, dataContainers, dynamicContext, label)); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /RuntimeEventHandlerExceptionEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Windows; 6 | 7 | namespace QuickConverter 8 | { 9 | public class RuntimeEventHandlerExceptionEventArgs : QuickConverterEventArgs 10 | { 11 | public override QuickConverterEventType Type { get { return QuickConverterEventType.RuntimeCodeException; } } 12 | 13 | public object Sender { get; private set; } 14 | 15 | public object EventArgs { get; private set; } 16 | 17 | public object V0 { get; private set; } 18 | public object V1 { get; private set; } 19 | public object V2 { get; private set; } 20 | public object V3 { get; private set; } 21 | public object V4 { get; private set; } 22 | public object V5 { get; private set; } 23 | public object V6 { get; private set; } 24 | public object V7 { get; private set; } 25 | public object V8 { get; private set; } 26 | public object V9 { get; private set; } 27 | 28 | public object P0 { get; private set; } 29 | public object P1 { get; private set; } 30 | public object P2 { get; private set; } 31 | public object P3 { get; private set; } 32 | public object P4 { get; private set; } 33 | 34 | public QuickEventHandler Handler { get; private set; } 35 | 36 | public Exception Exception { get; private set; } 37 | 38 | public string DebugView { get; private set; } 39 | 40 | internal RuntimeEventHandlerExceptionEventArgs(object sender, object eventArgs, string expression, string debugView, object[] values, QuickEventHandler handler, Exception exception) 41 | : base(expression) 42 | { 43 | Sender = sender; 44 | EventArgs = eventArgs; 45 | DebugView = debugView; 46 | V0 = values[0]; 47 | V1 = values[1]; 48 | V2 = values[2]; 49 | V3 = values[3]; 50 | V4 = values[4]; 51 | V5 = values[5]; 52 | V6 = values[6]; 53 | V7 = values[7]; 54 | V8 = values[8]; 55 | V9 = values[9]; 56 | if (sender is DependencyObject) 57 | { 58 | P0 = QuickEvent.GetP0(sender as DependencyObject); 59 | P1 = QuickEvent.GetP1(sender as DependencyObject); 60 | P2 = QuickEvent.GetP2(sender as DependencyObject); 61 | P3 = QuickEvent.GetP3(sender as DependencyObject); 62 | P4 = QuickEvent.GetP4(sender as DependencyObject); 63 | } 64 | Handler = handler; 65 | Exception = exception; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Tokens/LambdaAssignmentToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | 8 | namespace QuickConverter.Tokens 9 | { 10 | public class LambdaAssignmentToken : TokenBase 11 | { 12 | private Type type; 13 | internal LambdaAssignmentToken(Type type) 14 | { 15 | this.type = type; 16 | } 17 | 18 | public override Type ReturnType { get { return typeof(object); } } 19 | 20 | public override TokenBase[] Children { get { return new[] { Value }; } } 21 | 22 | public string Name { get; private set; } 23 | public MemberInfo Member { get; private set; } 24 | public TokenBase Value { get; private set; } 25 | 26 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 27 | { 28 | token = null; 29 | string temp = text; 30 | if (temp.Length == 0 || (!Char.IsLetter(temp[0]) && temp[0] != '_')) 31 | return false; 32 | int count = 1; 33 | while (count < temp.Length && (Char.IsLetterOrDigit(temp[count]) || temp[count] == '_')) 34 | ++count; 35 | string name = temp.Substring(0, count); 36 | 37 | MemberInfo info = null; 38 | if (type != null) 39 | { 40 | info = type.GetMember(name).FirstOrDefault(); 41 | if (info == null) 42 | return false; 43 | } 44 | else 45 | { 46 | string nameTemp = "$" + name; 47 | TokenBase tokenTemp; 48 | if (!new ParameterToken().TryGetToken(ref nameTemp, out tokenTemp)) 49 | return false; 50 | } 51 | 52 | temp = temp.Substring(count).TrimStart(); 53 | if (temp.Length == 0 || temp[0] != '=') 54 | return false; 55 | temp = temp.Substring(1).TrimStart(); 56 | TokenBase valToken; 57 | if (!EquationTokenizer.TryEvaluateExpression(temp, out valToken)) 58 | return false; 59 | text = ""; 60 | token = new LambdaAssignmentToken(null) { Name = name, Member = info, Value = valToken }; 61 | return true; 62 | } 63 | 64 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 65 | { 66 | return Value.GetExpression(parameters, locals, dataContainers, dynamicContext, label); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tokens/NullCoalesceOperatorToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using Microsoft.CSharp.RuntimeBinder; 8 | 9 | namespace QuickConverter.Tokens 10 | { 11 | public class NullCoalesceOperatorToken : TokenBase 12 | { 13 | internal NullCoalesceOperatorToken() 14 | { 15 | } 16 | 17 | public override Type ReturnType { get { return Condition.ReturnType == OnNull.ReturnType ? Condition.ReturnType : typeof(object); } } 18 | 19 | public override TokenBase[] Children { get { return new[] { Condition, OnNull }; } } 20 | 21 | public TokenBase Condition { get; private set; } 22 | public TokenBase OnNull { get; private set; } 23 | 24 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 25 | { 26 | token = null; 27 | bool inQuotes = false; 28 | int brackets = 0; 29 | int i = 0; 30 | int qPos = -1; 31 | while (true) 32 | { 33 | if (i >= text.Length - 1) 34 | return false; 35 | if (i > 0 && text[i] == '\'' && text[i - 1] != '\\') 36 | inQuotes = !inQuotes; 37 | else if (!inQuotes) 38 | { 39 | if (text[i] == '(') 40 | ++brackets; 41 | else if (text[i] == ')') 42 | --brackets; 43 | else if (brackets == 0 && text[i] == '?' && text[i + 1] == '?') 44 | { 45 | qPos = i; 46 | break; 47 | } 48 | } 49 | ++i; 50 | } 51 | TokenBase left, right; 52 | if (!EquationTokenizer.TryEvaluateExpression(text.Substring(0, qPos).Trim(), out left)) 53 | return false; 54 | if (!EquationTokenizer.TryEvaluateExpression(text.Substring(qPos + 2).Trim(), out right)) 55 | return false; 56 | token = new NullCoalesceOperatorToken() { Condition = left, OnNull = right }; 57 | text = ""; 58 | return true; 59 | } 60 | 61 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 62 | { 63 | Expression c = Condition.GetExpression(parameters, locals, dataContainers, dynamicContext, label); 64 | Expression n = OnNull.GetExpression(parameters, locals, dataContainers, dynamicContext, label); 65 | return Expression.Coalesce(Expression.Convert(c, typeof(object)), Expression.Convert(n, typeof(object))); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Tokens/TypeCastToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using Microsoft.CSharp.RuntimeBinder; 8 | 9 | namespace QuickConverter.Tokens 10 | { 11 | public class TypeCastToken : TokenBase 12 | { 13 | internal TypeCastToken(bool parseTarget = true) 14 | { 15 | this.parseTarget = parseTarget; 16 | } 17 | 18 | public override Type ReturnType { get { return TargetType; } } 19 | 20 | public override TokenBase[] Children { get { return new[] { Target }; } } 21 | 22 | public Type TargetType { get; private set; } 23 | 24 | public TokenBase Target { get; internal set; } 25 | 26 | private bool parseTarget; 27 | 28 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 29 | { 30 | token = null; 31 | bool inQuotes = false; 32 | int brackets = 0; 33 | int i = 0; 34 | while (true) 35 | { 36 | if (i >= text.Length) 37 | return false; 38 | if (i > 0 && text[i] == '\'' && text[i - 1] != '\\') 39 | inQuotes = !inQuotes; 40 | else if (!inQuotes) 41 | { 42 | if (text[i] == '(') 43 | ++brackets; 44 | else if (text[i] == ')') 45 | { 46 | --brackets; 47 | if (brackets == 0) 48 | break; 49 | } 50 | } 51 | ++i; 52 | } 53 | string temp = text.Substring(1, i - 1); 54 | 55 | var tuple = GetNameMatches(temp, null, null).Where(tup => tup.Item1 is Type).Reverse().FirstOrDefault(); 56 | if (tuple == null) 57 | return false; 58 | 59 | temp = text.Substring(i + 1).TrimStart(); 60 | TokenBase valToken = null; 61 | if (parseTarget && !EquationTokenizer.TryGetValueToken(ref temp, out valToken)) 62 | return false; 63 | text = temp; 64 | token = new TypeCastToken() { TargetType = tuple.Item1 as Type, Target = valToken }; 65 | return true; 66 | } 67 | 68 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 69 | { 70 | CallSiteBinder binder = Binder.Convert(CSharpBinderFlags.ConvertExplicit, TargetType, dynamicContext ?? typeof(object)); 71 | return Expression.Convert(Expression.Dynamic(binder, TargetType, Target.GetExpression(parameters, locals, dataContainers, dynamicContext, label)), typeof(object)); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Tokens/IndexerToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using Microsoft.CSharp.RuntimeBinder; 8 | 9 | namespace QuickConverter.Tokens 10 | { 11 | public class IndexerToken : TokenBase, IPostToken 12 | { 13 | internal IndexerToken() 14 | { 15 | } 16 | 17 | public override Type ReturnType { get { return typeof(object); } } 18 | 19 | public override TokenBase[] Children { get { return new[] { Indices, Target }; } } 20 | 21 | public ArgumentListToken Indices { get; private set; } 22 | public TokenBase Target { get; private set; } 23 | 24 | internal override bool SetPostTarget(TokenBase target) 25 | { 26 | Target = target; 27 | return true; 28 | } 29 | 30 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 31 | { 32 | token = null; 33 | string temp = text; 34 | if (temp.Length < 3 || temp[0] != '[') 35 | return false; 36 | 37 | bool inQuotes = false; 38 | int brackets = 0; 39 | int i = 0; 40 | while (true) 41 | { 42 | if (i >= temp.Length) 43 | return false; 44 | if (i > 0 && temp[i] == '\'' && temp[i - 1] != '\\') 45 | inQuotes = !inQuotes; 46 | else if (!inQuotes) 47 | { 48 | if (temp[i] == '[') 49 | ++brackets; 50 | else if (temp[i] == ']') 51 | { 52 | --brackets; 53 | if (brackets == 0) 54 | break; 55 | } 56 | } 57 | ++i; 58 | } 59 | 60 | TokenBase ind; 61 | if (!new ArgumentListToken('[', ']').TryGetToken(ref temp, out ind)) 62 | return false; 63 | text = text.Substring(i + 1); 64 | token = new IndexerToken() { Indices = ind as ArgumentListToken }; 65 | return true; 66 | } 67 | 68 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 69 | { 70 | CallSiteBinder binder = Binder.GetIndex(CSharpBinderFlags.None, dynamicContext ?? typeof(object), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); 71 | return Expression.Dynamic(binder, typeof(object), new Expression[] { Target.GetExpression(parameters, locals, dataContainers, dynamicContext, label) }.Concat(Indices.Arguments.Select(token => token.GetExpression(parameters, locals, dataContainers, dynamicContext, label)))); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /RuntimeMultiConvertExceptionEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace QuickConverter 7 | { 8 | public class RuntimeMultiConvertExceptionEventArgs : QuickConverterEventArgs 9 | { 10 | public override QuickConverterEventType Type { get { return QuickConverterEventType.RuntimeCodeException; } } 11 | 12 | public object P0 { get; private set; } 13 | public object P1 { get; private set; } 14 | public object P2 { get; private set; } 15 | public object P3 { get; private set; } 16 | public object P4 { get; private set; } 17 | public object P5 { get; private set; } 18 | public object P6 { get; private set; } 19 | public object P7 { get; private set; } 20 | public object P8 { get; private set; } 21 | public object P9 { get; private set; } 22 | 23 | public object V0 { get; private set; } 24 | public object V1 { get; private set; } 25 | public object V2 { get; private set; } 26 | public object V3 { get; private set; } 27 | public object V4 { get; private set; } 28 | public object V5 { get; private set; } 29 | public object V6 { get; private set; } 30 | public object V7 { get; private set; } 31 | public object V8 { get; private set; } 32 | public object V9 { get; private set; } 33 | 34 | public object Value { get; private set; } 35 | 36 | public object Parameter { get; private set; } 37 | 38 | public DynamicMultiConverter Converter { get; private set; } 39 | 40 | public Exception Exception { get; private set; } 41 | 42 | public string DebugView { get; private set; } 43 | 44 | internal RuntimeMultiConvertExceptionEventArgs(string expression, string debugView, object[] p, int[] pIndices, object value, object[] values, object parameter, DynamicMultiConverter converter, Exception exception) 45 | : base(expression) 46 | { 47 | DebugView = debugView; 48 | if (p != null) 49 | { 50 | for (int i = 0; i < p.Length; ++i) 51 | { 52 | switch (pIndices[i]) 53 | { 54 | case 0: P0 = p[i]; break; 55 | case 1: P1 = p[i]; break; 56 | case 2: P2 = p[i]; break; 57 | case 3: P3 = p[i]; break; 58 | case 4: P4 = p[i]; break; 59 | case 5: P5 = p[i]; break; 60 | case 6: P6 = p[i]; break; 61 | case 7: P7 = p[i]; break; 62 | case 8: P8 = p[i]; break; 63 | case 9: P9 = p[i]; break; 64 | } 65 | } 66 | } 67 | V0 = values[0]; 68 | V1 = values[1]; 69 | V2 = values[2]; 70 | V3 = values[3]; 71 | V4 = values[4]; 72 | V5 = values[5]; 73 | V6 = values[6]; 74 | V7 = values[7]; 75 | V8 = values[8]; 76 | V9 = values[9]; 77 | Value = value; 78 | Parameter = parameter; 79 | Converter = converter; 80 | Exception = exception; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /QuickValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Windows.Data; 6 | using System.Windows.Markup; 7 | 8 | namespace QuickConverter 9 | { 10 | public class QuickValue : MarkupExtension 11 | { 12 | /// 13 | /// The expression to use for calculating the value. 14 | /// 15 | public string Value { get; set; } 16 | 17 | /// Creates a constant parameter. This can be accessed inside the value expression as $V0. 18 | public object V0 { get; set; } 19 | /// Creates a constant parameter. This can be accessed inside the value expression as $V1. 20 | public object V1 { get; set; } 21 | /// Creates a constant parameter. This can be accessed inside the value expression as $V2. 22 | public object V2 { get; set; } 23 | /// Creates a constant parameter. This can be accessed inside the value expression as $V3. 24 | public object V3 { get; set; } 25 | /// Creates a constant parameter. This can be accessed inside the value expression as $V4. 26 | public object V4 { get; set; } 27 | /// Creates a constant parameter. This can be accessed inside the value expression as $V5. 28 | public object V5 { get; set; } 29 | /// Creates a constant parameter. This can be accessed inside the value expression as $V6. 30 | public object V6 { get; set; } 31 | /// Creates a constant parameter. This can be accessed inside the value expression as $V7. 32 | public object V7 { get; set; } 33 | /// Creates a constant parameter. This can be accessed inside the value expression as $V8. 34 | public object V8 { get; set; } 35 | /// Creates a constant parameter. This can be accessed inside the value expression as $V9. 36 | public object V9 { get; set; } 37 | 38 | /// 39 | /// This specifies the context to use for dynamic call sites. 40 | /// 41 | public Type DynamicContext { get; set; } 42 | 43 | public QuickValue() 44 | { 45 | } 46 | public QuickValue(string valueExpression) 47 | { 48 | Value = valueExpression; 49 | } 50 | public override object ProvideValue(IServiceProvider serviceProvider) 51 | { 52 | try 53 | { 54 | var converter = new QuickConverter(Value) 55 | { 56 | V0 = V0, 57 | V1 = V1, 58 | V2 = V2, 59 | V3 = V3, 60 | V4 = V4, 61 | V5 = V5, 62 | V6 = V6, 63 | V7 = V7, 64 | V8 = V8, 65 | V9 = V9, 66 | DynamicContext = DynamicContext 67 | }; 68 | var value = (converter.ProvideValue(null) as IValueConverter).Convert(null, typeof(object), null, null); 69 | if (value is MarkupExtension) 70 | return (value as MarkupExtension).ProvideValue(serviceProvider); 71 | return value; 72 | } 73 | catch (Exception e) 74 | { 75 | EquationTokenizer.ThrowQuickConverterEvent(new MarkupExtensionExceptionEventArgs(Value, this, e)); 76 | throw; 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Tokens/TernaryOperatorToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using Microsoft.CSharp.RuntimeBinder; 8 | 9 | namespace QuickConverter.Tokens 10 | { 11 | public class TernaryOperatorToken : TokenBase 12 | { 13 | internal TernaryOperatorToken() 14 | { 15 | } 16 | 17 | public override Type ReturnType { get { return OnTrue.ReturnType == OnFalse.ReturnType ? OnTrue.ReturnType : typeof(object); } } 18 | 19 | public override TokenBase[] Children { get { return new[] { Condition, OnTrue, OnFalse }; } } 20 | 21 | public TokenBase Condition { get; private set; } 22 | public TokenBase OnTrue { get; private set; } 23 | public TokenBase OnFalse { get; private set; } 24 | 25 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 26 | { 27 | token = null; 28 | bool inQuotes = false; 29 | int brackets = 0; 30 | int count = 0; 31 | int i = 0; 32 | int qPos = -1; 33 | int cPos = -1; 34 | while (true) 35 | { 36 | if (i >= text.Length) 37 | return false; 38 | if (i > 0 && text[i] == '\'' && text[i - 1] != '\\') 39 | inQuotes = !inQuotes; 40 | else if (!inQuotes) 41 | { 42 | if (text[i] == '(') 43 | ++brackets; 44 | else if (text[i] == ')') 45 | --brackets; 46 | else if (brackets == 0) 47 | { 48 | if (text[i] == '?' && (i >= text.Length - 1 || (text[i + 1] != '.' && text[i + 1] != '['))) 49 | { 50 | if (count == 0) 51 | qPos = i; 52 | ++count; 53 | } 54 | else if (text[i] == ':') 55 | { 56 | --count; 57 | if (count < 0) 58 | return false; 59 | if (count == 0) 60 | { 61 | cPos = i; 62 | break; 63 | } 64 | } 65 | } 66 | } 67 | ++i; 68 | } 69 | TokenBase left, middle, right; 70 | if (!EquationTokenizer.TryEvaluateExpression(text.Substring(0, qPos).Trim(), out left)) 71 | return false; 72 | if (!EquationTokenizer.TryEvaluateExpression(text.Substring(qPos + 1, cPos - qPos - 1).Trim(), out middle)) 73 | return false; 74 | if (!EquationTokenizer.TryEvaluateExpression(text.Substring(cPos + 1).Trim(), out right)) 75 | return false; 76 | token = new TernaryOperatorToken() { Condition = left, OnTrue = middle, OnFalse = right }; 77 | text = ""; 78 | return true; 79 | } 80 | 81 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 82 | { 83 | CallSiteBinder binder = Binder.Convert(CSharpBinderFlags.None, typeof(bool), typeof(object)); 84 | Expression c = Condition.GetExpression(parameters, locals, dataContainers, dynamicContext, label); 85 | Expression t = OnTrue.GetExpression(parameters, locals, dataContainers, dynamicContext, label); 86 | Expression f = OnFalse.GetExpression(parameters, locals, dataContainers, dynamicContext, label); 87 | return Expression.Condition(Expression.Dynamic(binder, typeof(bool), c), Expression.Convert(t, typeof(object)), Expression.Convert(f, typeof(object))); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Tokens/BinaryOperatorToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using Microsoft.CSharp.RuntimeBinder; 8 | 9 | namespace QuickConverter.Tokens 10 | { 11 | public class BinaryOperatorToken : TokenBase 12 | { 13 | private static ExpressionType[] types = new[] 14 | { 15 | default(ExpressionType), 16 | default(ExpressionType), 17 | default(ExpressionType), 18 | ExpressionType.Multiply, 19 | ExpressionType.Divide, 20 | ExpressionType.Modulo, 21 | ExpressionType.Add, 22 | ExpressionType.Subtract, 23 | ExpressionType.GreaterThanOrEqual, 24 | ExpressionType.LessThanOrEqual, 25 | ExpressionType.GreaterThan, 26 | ExpressionType.LessThan, 27 | ExpressionType.Equal, 28 | ExpressionType.NotEqual, 29 | ExpressionType.AndAlso, 30 | ExpressionType.AndAlso, 31 | ExpressionType.OrElse, 32 | ExpressionType.And, 33 | ExpressionType.And, 34 | ExpressionType.Or, 35 | ExpressionType.ExclusiveOr, 36 | default(ExpressionType) 37 | }; 38 | 39 | public override Type ReturnType { get { return Operation >= Operator.GreaterOrEqual && Operation <= Operator.Or ? typeof(bool) : typeof(object); } } 40 | 41 | public override TokenBase[] Children { get { return new[] { Left, Right }; } } 42 | 43 | public TokenBase Left { get; private set; } 44 | public TokenBase Right { get; private set; } 45 | public Operator Operation { get; internal set; } 46 | 47 | internal BinaryOperatorToken(TokenBase left, TokenBase right, Operator operation) 48 | { 49 | this.Left = left; 50 | this.Right = right; 51 | this.Operation = operation; 52 | } 53 | 54 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 55 | { 56 | throw new NotImplementedException(); 57 | } 58 | 59 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 60 | { 61 | if (Operation == Operator.And || Operation == Operator.AlternateAnd) 62 | return Expression.Convert(Expression.AndAlso(Expression.Convert(Left.GetExpression(parameters, locals, dataContainers, dynamicContext, label), typeof(bool)), Expression.Convert(Right.GetExpression(parameters, locals, dataContainers, dynamicContext, label), typeof(bool))), typeof(object)); 63 | if (Operation == Operator.Or) 64 | return Expression.Convert(Expression.OrElse(Expression.Convert(Left.GetExpression(parameters, locals, dataContainers, dynamicContext, label), typeof(bool)), Expression.Convert(Right.GetExpression(parameters, locals, dataContainers, dynamicContext, label), typeof(bool))), typeof(object)); 65 | CallSiteBinder binder = Binder.BinaryOperation(CSharpBinderFlags.None, types[(int)Operation], dynamicContext ?? typeof(object), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); 66 | return Expression.Dynamic(binder, typeof(object), Left.GetExpression(parameters, locals, dataContainers, dynamicContext, label), Right.GetExpression(parameters, locals, dataContainers, dynamicContext, label)); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tokens/StaticFunctionToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Runtime.CompilerServices; 7 | using System.Text; 8 | using Microsoft.CSharp.RuntimeBinder; 9 | using Binder = Microsoft.CSharp.RuntimeBinder.Binder; 10 | 11 | namespace QuickConverter.Tokens 12 | { 13 | public class StaticFunctionToken : TokenBase 14 | { 15 | internal StaticFunctionToken() 16 | { 17 | } 18 | 19 | public override Type ReturnType { get { return Method.ReturnType; } } 20 | 21 | public override TokenBase[] Children { get { return new[] { Arguments }; } } 22 | 23 | public MethodInfo Method { get; private set; } 24 | public ArgumentListToken Arguments { get; private set; } 25 | 26 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 27 | { 28 | token = null; 29 | var list = GetNameMatches(text, null, null).Where(tup => tup.Item1 is MethodInfo && ((tup.Item1 as MethodInfo).ReturnType != typeof(void) || !requireReturnValue)).ToArray(); 30 | Tuple info = null; 31 | foreach (var method in list) 32 | { 33 | string temp = method.Item2; 34 | TokenBase args; 35 | if (!new ArgumentListToken('(', ')').TryGetToken(ref temp, out args)) 36 | continue; 37 | if ((args as ArgumentListToken).Arguments.Length <= (method.Item1 as MethodInfo).GetParameters().Length) 38 | { 39 | bool good = true; 40 | for (int i = 0; i < (method.Item1 as MethodInfo).GetParameters().Length; ++i) 41 | { 42 | if (i < (args as ArgumentListToken).Arguments.Length) 43 | { 44 | if ((args as ArgumentListToken).Arguments[i].ReturnType.IsAssignableFrom((method.Item1 as MethodInfo).GetParameters()[i].ParameterType) || (method.Item1 as MethodInfo).GetParameters()[i].ParameterType.IsAssignableFrom((args as ArgumentListToken).Arguments[i].ReturnType)) 45 | continue; 46 | } 47 | else if ((method.Item1 as MethodInfo).GetParameters()[i].IsOptional) 48 | continue; 49 | good = false; 50 | break; 51 | } 52 | if (!good) 53 | continue; 54 | info = new Tuple(method.Item1 as MethodInfo, args, temp); 55 | break; 56 | } 57 | } 58 | if (info == null) 59 | return false; 60 | text = info.Item3; 61 | token = new StaticFunctionToken() { Arguments = info.Item2 as ArgumentListToken, Method = info.Item1 }; 62 | return true; 63 | } 64 | 65 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 66 | { 67 | ParameterInfo[] pars = Method.GetParameters(); 68 | Expression[] args = new Expression[pars.Length]; 69 | for (int i = 0; i < pars.Length; ++i) 70 | { 71 | if (i < Arguments.Arguments.Length) 72 | { 73 | CallSiteBinder binder = Binder.Convert(CSharpBinderFlags.None, pars[i].ParameterType, dynamicContext ?? typeof(object)); 74 | args[i] = Expression.Dynamic(binder, pars[i].ParameterType, Arguments.Arguments[i].GetExpression(parameters, locals, dataContainers, dynamicContext, label)); 75 | } 76 | else 77 | { 78 | CallSiteBinder binder = Binder.Convert(CSharpBinderFlags.None, pars[i].ParameterType, dynamicContext ?? typeof(object)); 79 | args[i] = Expression.Dynamic(binder, pars[i].ParameterType, Expression.Constant(pars[i].DefaultValue, typeof(object))); 80 | } 81 | } 82 | var exp = Expression.Call(Method, args); 83 | if (requiresReturnValue) 84 | return Expression.Convert(exp, typeof(object)); 85 | return exp; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Tokens/ArgumentListToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | 7 | namespace QuickConverter.Tokens 8 | { 9 | public class ArgumentListToken : TokenBase 10 | { 11 | private char open; 12 | private char close; 13 | private bool findAssignments; 14 | private Type assignmentType; 15 | private bool allowSubLists; 16 | private bool allowTypeCasts; 17 | 18 | public override Type ReturnType { get { return typeof(object); } } 19 | 20 | public override TokenBase[] Children { get { return Arguments.ToArray(); } } 21 | 22 | public TokenBase[] Arguments { get; private set; } 23 | 24 | internal ArgumentListToken(char open, char close, Type assignmentType) 25 | { 26 | this.open = open; 27 | this.close = close; 28 | findAssignments = true; 29 | this.assignmentType = assignmentType; 30 | allowSubLists = false; 31 | allowTypeCasts = false; 32 | } 33 | 34 | internal ArgumentListToken(char open, char close, bool allowSubLists = false) 35 | { 36 | this.open = open; 37 | this.close = close; 38 | findAssignments = false; 39 | assignmentType = null; 40 | this.allowSubLists = allowSubLists; 41 | allowTypeCasts = false; 42 | } 43 | 44 | internal ArgumentListToken(bool allowTypeCasts, char open, char close) 45 | { 46 | this.open = open; 47 | this.close = close; 48 | findAssignments = false; 49 | assignmentType = null; 50 | allowSubLists = false; 51 | this.allowTypeCasts = allowTypeCasts; 52 | } 53 | 54 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 55 | { 56 | token = null; 57 | var list = new List(); 58 | List split; 59 | string temp = text; 60 | if (!TrySplitByCommas(ref temp, open, close, out split)) 61 | return false; 62 | foreach (string str in split) 63 | { 64 | TokenBase newToken; 65 | string s = str.Trim(); 66 | if (allowSubLists && s.StartsWith(open.ToString()) && s.EndsWith(close.ToString())) 67 | { 68 | if (new ArgumentListToken(open, close).TryGetToken(ref s, out newToken)) 69 | list.Add(newToken); 70 | else 71 | return false; 72 | } 73 | else if (findAssignments) 74 | { 75 | if (new LambdaAssignmentToken(assignmentType).TryGetToken(ref s, out newToken)) 76 | list.Add(newToken); 77 | else 78 | return false; 79 | } 80 | else if (allowTypeCasts) 81 | { 82 | if (new TypeCastToken(false).TryGetToken(ref s, out newToken)) 83 | { 84 | string nameTemp = "$" + s; 85 | TokenBase tokenTemp; 86 | if (!new ParameterToken().TryGetToken(ref nameTemp, out tokenTemp) || !String.IsNullOrWhiteSpace(nameTemp)) 87 | return false; 88 | (newToken as TypeCastToken).Target = tokenTemp; 89 | list.Add(newToken); 90 | } 91 | else 92 | { 93 | string nameTemp = "$" + s; 94 | TokenBase tokenTemp; 95 | if (!new ParameterToken().TryGetToken(ref nameTemp, out tokenTemp) || !String.IsNullOrWhiteSpace(nameTemp)) 96 | return false; 97 | list.Add(tokenTemp); 98 | } 99 | } 100 | else 101 | { 102 | if (EquationTokenizer.TryEvaluateExpression(str.Trim(), out newToken)) 103 | list.Add(newToken); 104 | else 105 | return false; 106 | } 107 | } 108 | token = new ArgumentListToken('\0', '\0') { Arguments = list.ToArray() }; 109 | text = temp; 110 | return true; 111 | } 112 | 113 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 114 | { 115 | throw new NotImplementedException(); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Tokens/AssignmentToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Runtime.CompilerServices; 7 | using System.Text; 8 | using Microsoft.CSharp.RuntimeBinder; 9 | using Binder = Microsoft.CSharp.RuntimeBinder.Binder; 10 | 11 | namespace QuickConverter.Tokens 12 | { 13 | public class AssignmentToken : TokenBase, IPostToken 14 | { 15 | internal AssignmentToken() 16 | { 17 | } 18 | 19 | public override Type ReturnType { get { return Value.ReturnType; } } 20 | 21 | public override TokenBase[] Children { get { return new[] { Target, Value }; } } 22 | 23 | public TokenBase Target { get; private set; } 24 | 25 | public TokenBase Value { get; private set; } 26 | 27 | public Operator Operator { get; private set; } 28 | 29 | internal override bool SetPostTarget(TokenBase target) 30 | { 31 | if (target is StaticMemberToken) 32 | { 33 | if ((target as StaticMemberToken).Member is PropertyInfo) 34 | { 35 | if (!((target as StaticMemberToken).Member as PropertyInfo).CanWrite) 36 | throw new Exception("Static member \"" + ((target as StaticMemberToken).Member as PropertyInfo).Name + "\" is readonly and cannot be set."); 37 | else if (Operator != default(Operator) && !((target as StaticMemberToken).Member as PropertyInfo).CanRead) 38 | throw new Exception("Static member \"" + ((target as StaticMemberToken).Member as PropertyInfo).Name + "\" is writeonly and cannot be read."); 39 | } 40 | else if (!((target as StaticMemberToken).Member is FieldInfo)) 41 | return false; 42 | } 43 | else if (!(target is InstanceMemberToken)) 44 | return false; 45 | Target = target; 46 | if (Operator != default(Operator)) 47 | Value = new BinaryOperatorToken(Target, Value, Operator); 48 | return true; 49 | } 50 | 51 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 52 | { 53 | token = null; 54 | string temp = text; 55 | var op = default(Operator); 56 | if (temp.Length < 2) 57 | return false; 58 | if (temp[0] != '=') 59 | { 60 | if (temp[1] != '=' || temp.Length < 3) 61 | return false; 62 | for (int i = (int)Operator.Multiply; i <= (int)Operator.Subtract; ++i) 63 | { 64 | if (EquationTokenizer.representations[i][0] == temp[0]) 65 | op = (Operator)i; 66 | } 67 | for (int i = (int)Operator.BitwiseAnd; i <= (int)Operator.BitwiseXor; ++i) 68 | { 69 | if (EquationTokenizer.representations[i][0] == temp[0]) 70 | op = (Operator)i; 71 | } 72 | if (op == default(Operator)) 73 | return false; 74 | } 75 | 76 | if (op == default(Operator)) 77 | temp = temp.Substring(1).TrimStart(); 78 | else 79 | temp = temp.Substring(2).TrimStart(); 80 | TokenBase valToken; 81 | if (!EquationTokenizer.TryEvaluateExpression(temp, out valToken)) 82 | return false; 83 | text = ""; 84 | token = new AssignmentToken() { Value = valToken, Operator = op }; 85 | return true; 86 | } 87 | 88 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 89 | { 90 | var value = Value.GetExpression(parameters, locals, dataContainers, dynamicContext, label); 91 | if (Target is InstanceMemberToken) 92 | { 93 | CallSiteBinder binder = Binder.SetMember(CSharpBinderFlags.None, (Target as InstanceMemberToken).MemberName, dynamicContext ?? typeof(object), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); 94 | return Expression.Dynamic(binder, typeof(object), (Target as InstanceMemberToken).Target.GetExpression(parameters, locals, dataContainers, dynamicContext, label), value); 95 | } 96 | else 97 | { 98 | var type = (Target as StaticMemberToken).Member is PropertyInfo ? ((Target as StaticMemberToken).Member as PropertyInfo).PropertyType : ((Target as StaticMemberToken).Member as FieldInfo).FieldType; 99 | CallSiteBinder binder = Binder.Convert(CSharpBinderFlags.None, type, dynamicContext ?? typeof(object)); 100 | return Expression.Convert(Expression.Assign(Expression.MakeMemberAccess(null, (Target as StaticMemberToken).Member), Expression.Dynamic(binder, type, value)), typeof(object)); 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Binding.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows; 9 | using System.Windows.Data; 10 | using System.Windows.Markup; 11 | using System.Xaml; 12 | 13 | namespace QuickConverter 14 | { 15 | /// 16 | /// This type can be substituted for System.Windows.Data.Binding. Both Convert and ConvertBack to be specified inline which makes two way binding possible. 17 | /// 18 | public class Binding : MarkupExtension 19 | { 20 | /// Creates a bound parameter. This can be accessed inside the converter as $P. 21 | public System.Windows.Data.Binding P { get; set; } 22 | 23 | /// Creates a constant parameter. This can be accessed inside the converter as $V0. 24 | public object V0 { get; set; } 25 | /// Creates a constant parameter. This can be accessed inside the converter as $V1. 26 | public object V1 { get; set; } 27 | /// Creates a constant parameter. This can be accessed inside the converter as $V2. 28 | public object V2 { get; set; } 29 | /// Creates a constant parameter. This can be accessed inside the converter as $V3. 30 | public object V3 { get; set; } 31 | /// Creates a constant parameter. This can be accessed inside the converter as $V4. 32 | public object V4 { get; set; } 33 | /// Creates a constant parameter. This can be accessed inside the converter as $V5. 34 | public object V5 { get; set; } 35 | /// Creates a constant parameter. This can be accessed inside the converter as $V6. 36 | public object V6 { get; set; } 37 | /// Creates a constant parameter. This can be accessed inside the converter as $V7. 38 | public object V7 { get; set; } 39 | /// Creates a constant parameter. This can be accessed inside the converter as $V8. 40 | public object V8 { get; set; } 41 | /// Creates a constant parameter. This can be accessed inside the converter as $V9. 42 | public object V9 { get; set; } 43 | 44 | /// 45 | /// The converter will return DependencyObject.Unset during conversion if P is not of this type. 46 | /// Both QuickConverter syntax (as a string) and Type objects are valid. 47 | /// 48 | public object PType { get; set; } 49 | /// 50 | /// The expression to use for converting data from the source. 51 | /// 52 | public string Convert { get; set; } 53 | /// 54 | /// The expression to use for converting data from the target back to the source. 55 | /// The target value is accessible as $value. 56 | /// The bound parameter $P cannot be accessed when converting back. 57 | /// 58 | public string ConvertBack { get; set; } 59 | 60 | /// 61 | /// This specifies the context to use for dynamic call sites. 62 | /// 63 | public Type DynamicContext { get; set; } 64 | 65 | public Binding() 66 | { 67 | } 68 | 69 | public Binding(string convert) 70 | { 71 | Convert = convert; 72 | } 73 | 74 | public override object ProvideValue(IServiceProvider serviceProvider) 75 | { 76 | try 77 | { 78 | if (P == null) 79 | return null; 80 | 81 | bool getExpression; 82 | if (serviceProvider == null) 83 | getExpression = false; 84 | else 85 | { 86 | var targetProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; 87 | if (targetProvider != null && (targetProvider.TargetObject is Setter)) 88 | getExpression = false; 89 | else if (targetProvider == null || !(targetProvider.TargetProperty is PropertyInfo)) 90 | getExpression = true; 91 | else 92 | { 93 | Type propType = (targetProvider.TargetProperty as PropertyInfo).PropertyType; 94 | if (propType == typeof(Binding)) 95 | return this; 96 | getExpression = !propType.IsAssignableFrom(typeof(System.Windows.Data.MultiBinding)); 97 | } 98 | } 99 | 100 | P.Converter = new QuickConverter() 101 | { 102 | Convert = Convert, 103 | ConvertBack = ConvertBack, 104 | DynamicContext = DynamicContext, 105 | PType = PType, 106 | V0 = V0, 107 | V1 = V1, 108 | V2 = V2, 109 | V3 = V3, 110 | V4 = V4, 111 | V5 = V5, 112 | V6 = V6, 113 | V7 = V7, 114 | V8 = V8, 115 | V9 = V9 116 | }.Get(); 117 | 118 | return getExpression ? P.ProvideValue(serviceProvider) : P; 119 | } 120 | catch (Exception e) 121 | { 122 | EquationTokenizer.ThrowQuickConverterEvent(new MarkupExtensionExceptionEventArgs(Convert, this, e)); 123 | throw; 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Tokens/ConstantToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | 8 | namespace QuickConverter.Tokens 9 | { 10 | public class ConstantToken : TokenBase 11 | { 12 | internal ConstantToken() 13 | { 14 | } 15 | 16 | public override Type ReturnType { get { return Value != null ? Value.GetType() : typeof(object); } } 17 | 18 | public override TokenBase[] Children { get { return new TokenBase[0]; } } 19 | 20 | public object Value { get; private set; } 21 | 22 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 23 | { 24 | token = null; 25 | if (text.Length == 0) 26 | return false; 27 | if (text[0] == '\'') 28 | { 29 | int count = 1; 30 | while (count < text.Length && !(text[count] == '\'' && text[count - 1] != '\\')) 31 | ++count; 32 | if (count > text.Length) 33 | return false; 34 | if (text.Length > count + 1 && text[count + 1] == 'c') 35 | { 36 | if (count > 2) 37 | throw new Exception("The string '" + text.Substring(1, count - 1) + "' can not be interpreted as a character."); 38 | token = new ConstantToken() { Value = text[1] }; 39 | ++count; 40 | } 41 | else 42 | token = new ConstantToken() { Value = text.Substring(1, count - 1) }; 43 | text = text.Substring(count + 1); 44 | return true; 45 | } 46 | if (text.Length >= 4 && text.Substring(0, 4).ToLower() == "true") 47 | { 48 | text = text.Substring(4); 49 | token = new ConstantToken() { Value = true }; 50 | return true; 51 | } 52 | if (text.Length >= 5 && text.Substring(0, 5).ToLower() == "false") 53 | { 54 | text = text.Substring(5); 55 | token = new ConstantToken() { Value = false }; 56 | return true; 57 | } 58 | if (text.Length >= 4 && text.Substring(0, 4).ToLower() == "null") 59 | { 60 | text = text.Substring(4); 61 | token = new ConstantToken() { Value = null }; 62 | return true; 63 | } 64 | { 65 | int count = 0; 66 | while (count < text.Length && (Char.IsDigit(text[count]) || text[count] == '.')) 67 | ++count; 68 | if (count > 0 && text[count - 1] == '.') 69 | --count; 70 | if (count == 0) 71 | return false; 72 | 73 | string temp = text.Substring(0, count); 74 | if (text.Length > count) 75 | { 76 | char c = Char.ToLower(text[count]); 77 | ++count; 78 | object ret = null; 79 | if (c == 'u' && text.Length > count + 1 && Char.ToLower(text[count]) == 'l') 80 | { 81 | ulong val; 82 | if (UInt64.TryParse(temp, NumberStyles.Any, new CultureInfo("en-us"), out val)) 83 | { 84 | ret = val; 85 | ++count; 86 | } 87 | } 88 | else if (c == 'u') 89 | { 90 | uint val; 91 | if (UInt32.TryParse(temp, NumberStyles.Any, new CultureInfo("en-us"), out val)) 92 | ret = val; 93 | } 94 | else if (c == 'l') 95 | { 96 | long val; 97 | if (Int64.TryParse(temp, NumberStyles.Any, new CultureInfo("en-us"), out val)) 98 | ret = val; 99 | } 100 | else if (c == 'f') 101 | { 102 | float val; 103 | if (Single.TryParse(temp, NumberStyles.Any, new CultureInfo("en-us"), out val)) 104 | ret = val; 105 | } 106 | else if (c == 'd') 107 | { 108 | double val; 109 | if (Double.TryParse(temp, NumberStyles.Any, new CultureInfo("en-us"), out val)) 110 | ret = val; 111 | } 112 | else if (c == 'm') 113 | { 114 | decimal val; 115 | if (Decimal.TryParse(temp, NumberStyles.Any, new CultureInfo("en-us"), out val)) 116 | ret = val; 117 | } 118 | if (ret != null) 119 | { 120 | text = text.Substring(count); 121 | token = new ConstantToken() { Value = ret }; 122 | return true; 123 | } 124 | --count; 125 | } 126 | double val1; 127 | int val2; 128 | if (temp.Contains('.')) 129 | { 130 | if (Double.TryParse(temp, NumberStyles.Any, new CultureInfo("en-us"), out val1)) 131 | { 132 | text = text.Substring(count); 133 | token = new ConstantToken() { Value = val1 }; 134 | return true; 135 | } 136 | } 137 | else if (Int32.TryParse(temp, NumberStyles.Any, new CultureInfo("en-us"), out val2)) 138 | { 139 | text = text.Substring(count); 140 | token = new ConstantToken() { Value = val2 }; 141 | return true; 142 | } 143 | } 144 | return false; 145 | } 146 | 147 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 148 | { 149 | return Expression.Constant(Value, typeof(object)); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /QuickEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Windows; 7 | 8 | namespace QuickConverter 9 | { 10 | public abstract class QuickEventHandler 11 | { 12 | public string HandlerExpression { get; private set; } 13 | 14 | public object[] Values { get; private set; } 15 | 16 | public string HandlerExpressionDebugView { get; private set; } 17 | 18 | public Exception LastException { get; protected set; } 19 | public int ExceptionCount { get; protected set; } 20 | 21 | protected Delegate _handler; 22 | protected string[] _parameters; 23 | protected DataContainer[] _dataContainers; 24 | 25 | public QuickEventHandler(Delegate handler, string[] parameters, object[] values, string expression, string expressionDebug, DataContainer[] dataContainers) 26 | { 27 | _handler = handler; 28 | _parameters = parameters; 29 | Values = values; 30 | HandlerExpression = expression; 31 | HandlerExpressionDebugView = expressionDebug; 32 | _dataContainers = dataContainers; 33 | } 34 | } 35 | 36 | internal class QuickEventHandler : QuickEventHandler 37 | { 38 | private object[] _parArray; 39 | private int _dataContextIndex = -1; 40 | private int _eventArgsIndex = -1; 41 | private int[] _pIndex = new int[] { -1, -1, -1, -1, -1 }; 42 | private bool _ignoreIfNotOriginalSource; 43 | private bool _setHandled; 44 | 45 | private object _lastSender; 46 | 47 | public QuickEventHandler(Delegate handler, string[] parameters, object[] values, string expression, string expressionDebug, DataContainer[] dataContainers, bool ignoreIfNotOriginalSource, bool setHandled) 48 | : base(handler, parameters, values, expression, expressionDebug, dataContainers) 49 | { 50 | _ignoreIfNotOriginalSource = ignoreIfNotOriginalSource; 51 | _setHandled = setHandled; 52 | } 53 | 54 | public void Handle(T1 sender, T2 args) 55 | { 56 | if (_ignoreIfNotOriginalSource && args is RoutedEventArgs && (args as RoutedEventArgs).OriginalSource != (args as RoutedEventArgs).Source) 57 | return; 58 | 59 | if (!SetupParameters(sender, args)) 60 | return; 61 | 62 | try { _handler.DynamicInvoke(_parArray); } 63 | catch (Exception e) 64 | { 65 | LastException = e; 66 | ++ExceptionCount; 67 | if (Debugger.IsAttached) 68 | Console.WriteLine("QuickEvent Exception (\"" + HandlerExpression + "\") - " + e.Message + (e.InnerException != null ? " (Inner - " + e.InnerException.Message + ")" : "")); 69 | EquationTokenizer.ThrowQuickConverterEvent(new RuntimeEventHandlerExceptionEventArgs(sender, args, HandlerExpression, HandlerExpressionDebugView, Values, this, e)); 70 | } 71 | finally 72 | { 73 | if (_dataContextIndex >= 0) 74 | _parArray[_dataContextIndex] = null; 75 | if (_eventArgsIndex >= 0) 76 | _parArray[_eventArgsIndex] = null; 77 | if (_dataContainers != null) 78 | { 79 | foreach (var container in _dataContainers) 80 | container.Value = null; 81 | } 82 | if (_setHandled && args is RoutedEventArgs) 83 | (args as RoutedEventArgs).Handled = true; 84 | } 85 | } 86 | 87 | private bool SetupParameters(object sender, object args) 88 | { 89 | if (_lastSender != sender) 90 | _parArray = null; 91 | _lastSender = sender; 92 | string failMessage = null; 93 | if (_parArray == null) 94 | { 95 | _parArray = new object[_parameters.Length]; 96 | for (int i = 0; i < _parameters.Length; ++i) 97 | { 98 | var par = _parameters[i]; 99 | switch (par) 100 | { 101 | case "sender": 102 | _parArray[i] = sender; 103 | break; 104 | case "eventArgs": 105 | _eventArgsIndex = i; 106 | break; 107 | case "dataContext": 108 | _dataContextIndex = i; 109 | break; 110 | default: 111 | if (par.Length == 2 && par[0] == 'V' && Char.IsDigit(par[1])) 112 | _parArray[i] = Values[par[1] - '0']; 113 | else if (par.Length == 2 && par[0] == 'P' && par[1] >= '0' && par[1] <= '4') 114 | _pIndex[par[1] - '0'] = i; 115 | else if (sender is FrameworkElement) 116 | { 117 | _parArray[i] = (sender as FrameworkElement).FindName(par); 118 | if (_parArray[i] == null) 119 | failMessage = "Could not find target for $" + par + "."; 120 | } 121 | else 122 | failMessage = "Sender is not a framework element. Finding targets by name only works when sender is a framework element."; 123 | break; 124 | } 125 | } 126 | } 127 | if (_dataContextIndex >= 0 && sender is FrameworkElement) 128 | _parArray[_dataContextIndex] = (sender as FrameworkElement).DataContext; 129 | if (_eventArgsIndex >= 0) 130 | _parArray[_eventArgsIndex] = args; 131 | for (int i = 0; i <= 4; ++i) 132 | { 133 | if (_pIndex[i] == -1) 134 | continue; 135 | if (!(sender is DependencyObject)) 136 | { 137 | failMessage = "Cannot access $P0-$P4 when sender is not a DependencyObject."; 138 | break; 139 | } 140 | if (i == 0) 141 | _parArray[_pIndex[i]] = QuickEvent.GetP0(sender as DependencyObject); 142 | else if (i == 1) 143 | _parArray[_pIndex[i]] = QuickEvent.GetP1(sender as DependencyObject); 144 | else if (i == 2) 145 | _parArray[_pIndex[i]] = QuickEvent.GetP2(sender as DependencyObject); 146 | else if (i == 3) 147 | _parArray[_pIndex[i]] = QuickEvent.GetP3(sender as DependencyObject); 148 | else if (i == 4) 149 | _parArray[_pIndex[i]] = QuickEvent.GetP4(sender as DependencyObject); 150 | } 151 | if (failMessage != null) 152 | { 153 | EquationTokenizer.ThrowQuickConverterEvent(new RuntimeEventHandlerExceptionEventArgs(sender, args, HandlerExpression, HandlerExpressionDebugView, Values, this, new Exception(failMessage))); 154 | return false; 155 | } 156 | return true; 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Tokens/LambdaToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Runtime.CompilerServices; 7 | using System.Text; 8 | using Microsoft.CSharp.RuntimeBinder; 9 | using Binder = Microsoft.CSharp.RuntimeBinder.Binder; 10 | 11 | namespace QuickConverter.Tokens 12 | { 13 | public class LambdaToken : TokenBase 14 | { 15 | private static Type[] funcTypes = new Type[] { typeof(Func<>), typeof(Func<,>), typeof(Func<,,>), typeof(Func<,,,>), typeof(Func<,,,,>), typeof(Func<,,,,,>), typeof(Func<,,,,,,>), typeof(Func<,,,,,,,>), typeof(Func<,,,,,,,,>), typeof(Func<,,,,,,,,,>), typeof(Func<,,,,,,,,,,>), typeof(Func<,,,,,,,,,,,>), typeof(Func<,,,,,,,,,,,,>), typeof(Func<,,,,,,,,,,,,,>), typeof(Func<,,,,,,,,,,,,,,,>), typeof(Func<,,,,,,,,,,,,,,,,>) }; 16 | 17 | internal LambdaToken() 18 | { 19 | } 20 | 21 | public override Type ReturnType { get { return typeof(object); } } 22 | 23 | public override TokenBase[] Children { get { return new[] { Value, Arguments }; } } 24 | 25 | public ArgumentListToken Arguments { get; private set; } 26 | public TokenBase Value { get; private set; } 27 | public bool Lambda { get; private set; } 28 | 29 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 30 | { 31 | token = null; 32 | TokenBase arguments; 33 | bool lambda = false; 34 | if (!new ArgumentListToken('(', ')', null).TryGetToken(ref text, out arguments)) 35 | { 36 | lambda = true; 37 | if (!new ArgumentListToken(true, '(', ')').TryGetToken(ref text, out arguments)) 38 | return false; 39 | } 40 | string temp = text.TrimStart(); 41 | if (!temp.StartsWith("=>")) 42 | return false; 43 | temp = temp.Substring(2).TrimStart(); 44 | TokenBase method; 45 | if (!EquationTokenizer.TryEvaluateExpression(temp, out method)) 46 | return false; 47 | text = ""; 48 | token = new LambdaToken() { Arguments = arguments as ArgumentListToken, Value = method, Lambda = lambda }; 49 | return true; 50 | } 51 | 52 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 53 | { 54 | if (Lambda) 55 | { 56 | List> pars = new List>(); 57 | foreach (TokenBase token in Arguments.Arguments) 58 | { 59 | if (token is TypeCastToken) 60 | pars.Add(new Tuple(((token as TypeCastToken).Target as ParameterToken).Name, (token as TypeCastToken).TargetType)); 61 | else 62 | pars.Add(new Tuple((token as ParameterToken).Name, typeof(object))); 63 | } 64 | Dictionary subLocals = new Dictionary(); 65 | foreach (var tuple in pars) 66 | { 67 | var container = new DataContainer(); 68 | subLocals.Add(tuple.Item1, Expression.Constant(container)); 69 | dataContainers.Add(container); 70 | } 71 | 72 | List parExps = new List(); 73 | Expression exp = Value.GetExpression(parExps, subLocals, dataContainers, dynamicContext, label); 74 | 75 | if (parExps.Count != 0) 76 | { 77 | foreach (ParameterExpression par in parExps) 78 | { 79 | if (!(parameters.Any(p => p.Name == par.Name) || locals.Any(l => l.Key == par.Name))) 80 | parameters.Add(par); 81 | var container = new DataContainer(); 82 | subLocals.Add(par.Name, Expression.Constant(container)); 83 | dataContainers.Add(container); 84 | } 85 | parExps.Clear(); 86 | exp = Value.GetExpression(parExps, subLocals, dataContainers, dynamicContext, label); 87 | } 88 | 89 | foreach (var tuple in pars) 90 | parExps.Add(Expression.Parameter(tuple.Item2, tuple.Item1)); 91 | 92 | CallSiteBinder binder = Binder.Convert(CSharpBinderFlags.None, Value.ReturnType, dynamicContext); 93 | 94 | Expression block = Expression.Block(subLocals.Zip(parExps, (l, p) => Expression.Assign(Expression.Property(l.Value, "Value"), Expression.Convert(p, typeof(object)))).Concat(new Expression[] { Expression.Dynamic(binder, Value.ReturnType, exp) })); 95 | 96 | Type type = funcTypes[pars.Count].MakeGenericType(pars.Select(t => t.Item2).Concat(new[] { Value.ReturnType }).ToArray()); 97 | MethodInfo method = typeof(Expression).GetMethods().FirstOrDefault(m => m.Name == "Lambda" && m.IsGenericMethod && m.GetParameters().Length == 2).MakeGenericMethod(type); 98 | object func = ((dynamic)method.Invoke(null, new object[] { block, parExps.ToArray() })).Compile(); 99 | 100 | Expression ret = Expression.Block(subLocals.Skip(parExps.Count).Select(kvp => Expression.Assign(Expression.Property(kvp.Value, "Value"), parameters.Select(p => new Tuple(p.Name, p)).Concat(locals.Select(k => new Tuple(k.Key, Expression.Property(k.Value, "Value")))).First(p => p.Item1 == kvp.Key).Item2)).Concat(new [] { Expression.Constant(func) as Expression })); 101 | 102 | return ret; 103 | } 104 | else 105 | { 106 | List newLocals = new List(); 107 | foreach (var arg in Arguments.Arguments.Cast()) 108 | { 109 | if (locals.Any(name => name.Key == arg.Name)) 110 | throw new Exception("Duplicate local variable name \"" + arg.Name + "\" found."); 111 | var container = new DataContainer(); 112 | var value = Expression.Constant(container); 113 | dataContainers.Add(container); 114 | newLocals.Add(value); 115 | locals.Add(arg.Name, value); 116 | } 117 | IEnumerable assignments = Arguments.Arguments.Cast().Zip(newLocals, (t, l) => Expression.Assign(Expression.Property(l, "Value"), t.Value.GetExpression(parameters, locals, dataContainers, dynamicContext, label))); 118 | Expression ret = Expression.Block(assignments.Cast().Concat(new Expression[] {Value.GetExpression(parameters, locals, dataContainers, dynamicContext, label)})); 119 | foreach (var arg in Arguments.Arguments.Cast()) 120 | locals.Remove(arg.Name); 121 | return ret; 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /QuickConverter.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {42D0A4C4-B3B6-491B-B175-9FC5B24DE873} 8 | Library 9 | Properties 10 | QuickConverter 11 | QuickConverter 12 | v4.0 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | false 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | false 34 | 35 | 36 | true 37 | 38 | 39 | QuickConverterKey.snk 40 | 41 | 42 | true 43 | bin\x86\Debug\ 44 | DEBUG;TRACE 45 | full 46 | x86 47 | prompt 48 | MinimumRecommendedRules.ruleset 49 | 50 | 51 | bin\x86\Release\ 52 | TRACE 53 | true 54 | pdbonly 55 | x86 56 | prompt 57 | MinimumRecommendedRules.ruleset 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 129 | -------------------------------------------------------------------------------- /Tokens/TokenBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | 8 | namespace QuickConverter.Tokens 9 | { 10 | public abstract class TokenBase 11 | { 12 | private static Dictionary brackets = new Dictionary(); 13 | static TokenBase() 14 | { 15 | brackets.Add('(', ')'); 16 | brackets.Add('[', ']'); 17 | brackets.Add('{', '}'); 18 | } 19 | 20 | internal static IEnumerable> GetNameMatches(string name, string previous, Type parent) 21 | { 22 | if (name.Length > 0 && (Char.IsLetter(name[0]) || name[0] == '_')) 23 | { 24 | int count = 1; 25 | while (count < name.Length && (Char.IsLetterOrDigit(name[count]) || name[count] == '_')) 26 | ++count; 27 | string val = (previous != null && parent == null ? previous + "." : "") + name.Substring(0, count); 28 | string temp = name.Substring(count).TrimStart(); 29 | List typeArgs = null; 30 | if (temp.Length > 0 && temp[0] == '[') 31 | { 32 | string old = temp; 33 | typeArgs = new List(); 34 | List split; 35 | if (TrySplitByCommas(ref temp, '[', ']', out split)) 36 | { 37 | if (split.Count == 0) 38 | { 39 | typeArgs = null; 40 | val += "[]"; 41 | } 42 | else 43 | { 44 | foreach (string str in split) 45 | { 46 | Tuple tuple = GetNameMatches(str.Trim(), null, null).FirstOrDefault(tp => tp.Item1 is Type && string.IsNullOrWhiteSpace(tp.Item2)); 47 | if (tuple == null) 48 | { 49 | typeArgs = null; 50 | temp = old; 51 | break; 52 | } 53 | typeArgs.Add(tuple.Item1 as Type); 54 | } 55 | } 56 | } 57 | else 58 | yield break; 59 | } 60 | bool more = temp.Length > 0 && temp[0] == '.'; 61 | if (parent == null) 62 | { 63 | Type type; 64 | if (EquationTokenizer.TryGetType(val, typeArgs != null ? typeArgs.ToArray() : null, out type)) 65 | { 66 | yield return new Tuple(type, temp); 67 | if (more) 68 | { 69 | foreach (var match in GetNameMatches(temp.Substring(1), val, type)) 70 | yield return match; 71 | } 72 | } 73 | if (more) 74 | { 75 | foreach (var match in GetNameMatches(temp.Substring(1), val, null)) 76 | yield return match; 77 | } 78 | } 79 | else 80 | { 81 | foreach (MemberInfo info in parent.GetMember(val, BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)) 82 | { 83 | if (info.MemberType == MemberTypes.Method) 84 | { 85 | MemberInfo ret = info; 86 | if ((typeArgs != null) != (info as MethodInfo).IsGenericMethodDefinition) 87 | continue; 88 | if ((info as MethodInfo).IsGenericMethodDefinition && typeArgs.Count != (info as MethodInfo).GetGenericArguments().Length) 89 | continue; 90 | if ((info as MethodInfo).IsGenericMethodDefinition) 91 | { 92 | try { ret = (info as MethodInfo).MakeGenericMethod(typeArgs.ToArray()); } 93 | catch { continue; } 94 | } 95 | yield return new Tuple(ret, temp); 96 | } 97 | else if (info.MemberType == MemberTypes.Field || info.MemberType == MemberTypes.Property) 98 | yield return new Tuple(info, temp); 99 | else if (info.MemberType == MemberTypes.NestedType) 100 | { 101 | yield return new Tuple(info, temp); 102 | if (temp.Length > 0 && temp[0] == '.') 103 | { 104 | foreach (var match in GetNameMatches(temp.Substring(1), val, info as Type)) 105 | yield return match; 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | protected static bool TrySplitByCommas(ref string str, char open, char close, out List split) 114 | { 115 | split = new List(); 116 | bool inQuotes = false; 117 | int lastPos = 1; 118 | int i = 0; 119 | Stack bracketing = new Stack(); 120 | while (true) 121 | { 122 | if (i >= str.Length) 123 | return false; 124 | if (i > 0 && str[i] == '\'' && str[i - 1] != '\\') 125 | inQuotes = !inQuotes; 126 | else if (!inQuotes) 127 | { 128 | if (bracketing.Count == 0) 129 | { 130 | if (str[i] == open) 131 | bracketing.Push(str[i]); 132 | } 133 | else 134 | { 135 | if (brackets.Keys.Contains(str[i])) 136 | bracketing.Push(str[i]); 137 | else if (brackets.Values.Contains(str[i])) 138 | { 139 | if (bracketing.Count == 1) 140 | { 141 | if (str[i] != close) 142 | return false; 143 | break; 144 | } 145 | if (brackets[bracketing.Peek()] != str[i]) 146 | return false; 147 | bracketing.Pop(); 148 | } 149 | else if (bracketing.Count == 1 && str[i] == ',') 150 | { 151 | split.Add(str.Substring(lastPos, i - lastPos)); 152 | lastPos = i + 1; 153 | } 154 | } 155 | } 156 | if (bracketing.Count == 0 && !Char.IsWhiteSpace(str[i])) 157 | return false; 158 | ++i; 159 | } 160 | if (i != 1) 161 | split.Add(str.Substring(lastPos, i - lastPos)); 162 | str = str.Substring(i + 1); 163 | return true; 164 | } 165 | 166 | public TokenBase[] FlattenTree() 167 | { 168 | return GetChildren().ToArray(); 169 | } 170 | 171 | internal IEnumerable GetChildren() 172 | { 173 | return new[] { this }.Concat(Children.SelectMany(t => t.GetChildren())); 174 | } 175 | 176 | internal virtual bool SetPostTarget(TokenBase target) 177 | { 178 | return false; 179 | } 180 | 181 | internal abstract bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true); 182 | 183 | public abstract Type ReturnType { get; } 184 | 185 | public abstract TokenBase[] Children { get; } 186 | 187 | public Expression GetExpression(out List parameters, out List dataContainers, Type dynamicContext = null, bool requiresReturnValue = true) 188 | { 189 | parameters = new List(); 190 | dataContainers = new List(); 191 | return GetExpression(parameters, new Dictionary(), dataContainers, dynamicContext, null, requiresReturnValue); 192 | } 193 | 194 | internal abstract Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /Tokens/InstanceFunctionToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Runtime.CompilerServices; 7 | using System.Text; 8 | using Microsoft.CSharp.RuntimeBinder; 9 | using Binder = Microsoft.CSharp.RuntimeBinder.Binder; 10 | 11 | namespace QuickConverter.Tokens 12 | { 13 | public class InstanceFunctionToken : TokenBase, IPostToken 14 | { 15 | private static PropertyInfo SupportsExtensions = typeof(EquationTokenizer).GetProperty("SupportsExtensionMethods", BindingFlags.NonPublic | BindingFlags.Static); 16 | private static MethodInfo GetMethod = typeof(EquationTokenizer).GetMethod("GetMethod", BindingFlags.NonPublic | BindingFlags.Static); 17 | private static MethodInfo InvokeMethod = typeof(MethodInfo).GetMethods().First(m => m.Name == "Invoke" && m.GetParameters().Length == 2); 18 | private static PropertyInfo Item1Prop = typeof(Tuple).GetProperty("Item1"); 19 | private static PropertyInfo Item2Prop = typeof(Tuple).GetProperty("Item2"); 20 | 21 | internal InstanceFunctionToken() 22 | { 23 | } 24 | 25 | public override Type ReturnType { get { return typeof(object); } } 26 | 27 | public override TokenBase[] Children { get { return new[] { Arguments, Target }; } } 28 | 29 | public string MethodName { get; private set; } 30 | public ArgumentListToken Arguments { get; private set; } 31 | public Type[] Types { get; private set; } 32 | public TokenBase Target { get; private set; } 33 | 34 | internal override bool SetPostTarget(TokenBase target) 35 | { 36 | Target = target; 37 | return true; 38 | } 39 | 40 | internal override bool TryGetToken(ref string text, out TokenBase token, bool requireReturnValue = true) 41 | { 42 | token = null; 43 | string temp = text; 44 | if (temp.Length < 2 || temp[0] != '.' || (!Char.IsLetter(temp[1]) && temp[1] != '_')) 45 | return false; 46 | int count = 2; 47 | while (count < temp.Length && (Char.IsLetterOrDigit(temp[count]) || temp[count] == '_')) 48 | ++count; 49 | string name = temp.Substring(1, count - 1); 50 | temp = temp.Substring(count).TrimStart(); 51 | if (temp.Length == 0) 52 | return false; 53 | List typeArgs = null; 54 | if (temp[0] == '[') 55 | { 56 | List list; 57 | if (!TrySplitByCommas(ref temp, '[', ']', out list)) 58 | return false; 59 | typeArgs = new List(); 60 | foreach (string str in list) 61 | { 62 | Tuple tuple = GetNameMatches(str.Trim(), null, null).FirstOrDefault(tp => tp.Item1 is Type && string.IsNullOrWhiteSpace(tp.Item2)); 63 | if (tuple == null) 64 | return false; 65 | typeArgs.Add(tuple.Item1 as Type); 66 | } 67 | } 68 | TokenBase args; 69 | if (!new ArgumentListToken('(', ')').TryGetToken(ref temp, out args)) 70 | return false; 71 | text = temp; 72 | token = new InstanceFunctionToken() { Arguments = args as ArgumentListToken, MethodName = name, Types = typeArgs != null ? typeArgs.ToArray() : null }; 73 | return true; 74 | } 75 | 76 | internal override Expression GetExpression(List parameters, Dictionary locals, List dataContainers, Type dynamicContext, LabelTarget label, bool requiresReturnValue = true) 77 | { 78 | CallSiteBinder binder = Binder.InvokeMember(requiresReturnValue ? CSharpBinderFlags.None : CSharpBinderFlags.ResultDiscarded, MethodName, Types, dynamicContext ?? typeof(object), new object[Arguments.Arguments.Length + 1].Select(val => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null))); 79 | Expression dynamicCall = Expression.Dynamic(binder, requiresReturnValue ? typeof(object) : typeof(void), new[] { Target.GetExpression(parameters, locals, dataContainers, dynamicContext, label) }.Concat(Arguments.Arguments.Select(token => token.GetExpression(parameters, locals, dataContainers, dynamicContext, label)))); 80 | 81 | var targetVar = Expression.Variable(typeof(object)); 82 | var argsVar = Expression.Variable(typeof(object[])); 83 | var methodVar = Expression.Variable(typeof(Tuple)); 84 | 85 | Expression ret; 86 | 87 | if (requiresReturnValue) 88 | { 89 | var resultVar = Expression.Variable(typeof(object)); 90 | 91 | Expression test = Expression.Equal(methodVar, Expression.Constant(null, typeof(Tuple))); 92 | Expression ifNotNull = Expression.Assign(resultVar, Expression.Call(Expression.Property(methodVar, Item1Prop), InvokeMethod, Expression.Constant(null), Expression.Property(methodVar, Item2Prop))); 93 | Expression ifNull = Expression.Assign(resultVar, dynamicCall); 94 | 95 | Expression branch = Expression.IfThenElse(test, ifNull, ifNotNull); 96 | 97 | Expression block = Expression.Block(new[] { targetVar, argsVar, methodVar }, new[] 98 | { 99 | Expression.Assign(targetVar, Target.GetExpression(parameters, locals, dataContainers, dynamicContext, label)), 100 | Expression.Assign(argsVar, Expression.NewArrayInit(typeof(object), new[] { targetVar }.Concat(Arguments.Arguments.Select(token => token.GetExpression(parameters, locals, dataContainers, dynamicContext, label))))), 101 | Expression.Assign(methodVar, Expression.Call(GetMethod, Expression.Constant(MethodName, typeof(string)), Expression.Constant(Types, typeof(Type[])), argsVar)), 102 | branch, 103 | resultVar 104 | }); 105 | 106 | ret = Expression.Block(new[] { resultVar }, new Expression[] { Expression.IfThenElse(Expression.Property(null, SupportsExtensions), block, ifNull), resultVar }); 107 | } 108 | else 109 | { 110 | Expression test = Expression.Equal(methodVar, Expression.Constant(null, typeof(Tuple))); 111 | Expression ifNotNull = Expression.Call(Expression.Property(methodVar, Item1Prop), InvokeMethod, Expression.Constant(null), Expression.Property(methodVar, Item2Prop)); 112 | Expression ifNull = dynamicCall; 113 | 114 | Expression branch = Expression.IfThenElse(test, ifNull, ifNotNull); 115 | 116 | Expression block = Expression.Block(typeof(void), new[] { targetVar, argsVar, methodVar }, new[] 117 | { 118 | Expression.Assign(targetVar, Target.GetExpression(parameters, locals, dataContainers, dynamicContext, label)), 119 | Expression.Assign(argsVar, Expression.NewArrayInit(typeof(object), new[] { targetVar }.Concat(Arguments.Arguments.Select(token => token.GetExpression(parameters, locals, dataContainers, dynamicContext, label))))), 120 | Expression.Assign(methodVar, Expression.Call(GetMethod, Expression.Constant(MethodName, typeof(string)), Expression.Constant(Types, typeof(Type[])), argsVar)), 121 | branch 122 | }); 123 | 124 | ret = Expression.IfThenElse(Expression.Property(null, SupportsExtensions), block, ifNull); 125 | } 126 | 127 | return ret; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /DynamicSingleConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Globalization; 6 | using System.Linq; 7 | using System.Linq.Expressions; 8 | using System.Text; 9 | using System.Windows; 10 | using System.Windows.Data; 11 | using Microsoft.CSharp.RuntimeBinder; 12 | using Expression = System.Linq.Expressions.Expression; 13 | 14 | namespace QuickConverter 15 | { 16 | public class DynamicSingleConverter : IValueConverter 17 | { 18 | private static Type _namedObjectType = typeof(DependencyObject).Assembly.GetType("MS.Internal.NamedObject", false); 19 | 20 | private static ConcurrentDictionary> castFunctions = new ConcurrentDictionary>(); 21 | 22 | public string ConvertExpression { get; private set; } 23 | public string ConvertBackExpression { get; private set; } 24 | public Exception LastException { get; private set; } 25 | public int ExceptionCount { get; private set; } 26 | 27 | public string ConvertExpressionDebugView { get; private set; } 28 | public string ConvertBackExpressionDebugView { get; private set; } 29 | 30 | public Type PType { get; private set; } 31 | 32 | public Type ValueType { get; private set; } 33 | 34 | public object[] Values { get { return _values; } } 35 | 36 | private Func _converter; 37 | private Func _convertBack; 38 | private object[] _values; 39 | private DataContainer[] _toDataContainers; 40 | private DataContainer[] _fromDataContainers; 41 | private IValueConverter _chainedConverter; 42 | public DynamicSingleConverter(Func converter, Func convertBack, object[] values, string convertExp, string convertExpDebug, string convertBackExp, string convertBackExpDebug, Type pType, Type vType, DataContainer[] toDataContainers, DataContainer[] fromDataContainers, IValueConverter chainedConverter) 43 | { 44 | _converter = converter; 45 | _convertBack = convertBack; 46 | _values = values; 47 | _toDataContainers = toDataContainers; 48 | _fromDataContainers = fromDataContainers; 49 | ConvertExpression = convertExp; 50 | ConvertBackExpression = convertBackExp; 51 | ConvertExpressionDebugView = convertExpDebug; 52 | ConvertBackExpressionDebugView = convertBackExpDebug; 53 | PType = pType; 54 | ValueType = vType; 55 | _chainedConverter = chainedConverter; 56 | } 57 | 58 | private object DoConversion(object value, Type targetType, object parameter, Func func, bool convertingBack, CultureInfo culture) 59 | { 60 | if (convertingBack) 61 | { 62 | if (_chainedConverter != null) 63 | { 64 | try { value = _chainedConverter.ConvertBack(value, targetType, parameter, culture); } 65 | catch (Exception e) 66 | { 67 | EquationTokenizer.ThrowQuickConverterEvent(new ChainedConverterExceptionEventArgs(ConvertExpression, value, targetType, parameter, culture, true, _chainedConverter, this, e)); 68 | return DependencyProperty.UnsetValue; 69 | } 70 | 71 | if (value == DependencyProperty.UnsetValue || value == System.Windows.Data.Binding.DoNothing) 72 | return value; 73 | 74 | } 75 | 76 | if (ValueType != null && !ValueType.IsInstanceOfType(value)) 77 | return System.Windows.Data.Binding.DoNothing; 78 | } 79 | else 80 | { 81 | if (value == DependencyProperty.UnsetValue || _namedObjectType.IsInstanceOfType(value) || (PType != null && !PType.IsInstanceOfType(value))) 82 | return DependencyProperty.UnsetValue; 83 | } 84 | 85 | object result = value; 86 | if (func != null) 87 | { 88 | try { result = func(result, _values, parameter); } 89 | catch (Exception e) 90 | { 91 | LastException = e; 92 | ++ExceptionCount; 93 | if (Debugger.IsAttached) 94 | Console.WriteLine("QuickMultiConverter Exception (\"" + (convertingBack ? ConvertBackExpression : ConvertExpression) + "\") - " + e.Message + (e.InnerException != null ? " (Inner - " + e.InnerException.Message + ")" : "")); 95 | if (convertingBack) 96 | EquationTokenizer.ThrowQuickConverterEvent(new RuntimeSingleConvertExceptionEventArgs(ConvertBackExpression, ConvertBackExpressionDebugView, null, value, _values, parameter, this, e)); 97 | else 98 | EquationTokenizer.ThrowQuickConverterEvent(new RuntimeSingleConvertExceptionEventArgs(ConvertExpression, ConvertExpressionDebugView, value, null, _values, parameter, this, e)); 99 | return DependencyProperty.UnsetValue; 100 | } 101 | finally 102 | { 103 | var dataContainers = convertingBack ? _fromDataContainers : _toDataContainers; 104 | if (dataContainers != null) 105 | { 106 | foreach (var container in dataContainers) 107 | container.Value = null; 108 | } 109 | } 110 | } 111 | 112 | if (result == DependencyProperty.UnsetValue || result == System.Windows.Data.Binding.DoNothing) 113 | return result; 114 | 115 | if (!convertingBack && _chainedConverter != null) 116 | { 117 | try { result = _chainedConverter.Convert(result, targetType, parameter, culture); } 118 | catch (Exception e) 119 | { 120 | EquationTokenizer.ThrowQuickConverterEvent(new ChainedConverterExceptionEventArgs(ConvertExpression, result, targetType, parameter, culture, false, _chainedConverter, this, e)); 121 | return DependencyProperty.UnsetValue; 122 | } 123 | } 124 | 125 | if (result == DependencyProperty.UnsetValue || result == System.Windows.Data.Binding.DoNothing || result == null || targetType == null || targetType == typeof(object)) 126 | return result; 127 | 128 | if (targetType == typeof(string)) 129 | return result.ToString(); 130 | 131 | Func cast; 132 | if (!castFunctions.TryGetValue(targetType, out cast)) 133 | { 134 | ParameterExpression par = Expression.Parameter(typeof(object)); 135 | cast = Expression.Lambda>(Expression.Convert(Expression.Dynamic(Binder.Convert(CSharpBinderFlags.ConvertExplicit, targetType, typeof(object)), targetType, par), typeof(object)), par).Compile(); 136 | castFunctions.TryAdd(targetType, cast); 137 | } 138 | if (cast != null) 139 | { 140 | try 141 | { 142 | result = cast(result); 143 | } 144 | catch 145 | { 146 | castFunctions[targetType] = null; 147 | } 148 | } 149 | return result; 150 | } 151 | 152 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 153 | { 154 | return DoConversion(value, targetType, parameter, _converter, false, culture); 155 | } 156 | 157 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 158 | { 159 | return DoConversion(value, targetType, parameter, _convertBack, true, culture); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /DynamicMultiConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Text; 8 | using System.Windows; 9 | using System.Windows.Data; 10 | using Microsoft.CSharp.RuntimeBinder; 11 | using Expression = System.Linq.Expressions.Expression; 12 | 13 | namespace QuickConverter 14 | { 15 | public class DynamicMultiConverter : IMultiValueConverter 16 | { 17 | private static Type _namedObjectType = typeof(DependencyObject).Assembly.GetType("MS.Internal.NamedObject", false); 18 | 19 | private static ConcurrentDictionary> castFunctions = new ConcurrentDictionary>(); 20 | 21 | public string ConvertExpression { get; private set; } 22 | public string[] ConvertBackExpression { get; private set; } 23 | public Exception LastException { get; private set; } 24 | public int ExceptionCount { get; private set; } 25 | 26 | public string ConvertExpressionDebugView { get; private set; } 27 | public string[] ConvertBackExpressionDebugView { get; private set; } 28 | 29 | public Type[] PTypes { get; private set; } 30 | 31 | public Type ValueType { get; private set; } 32 | 33 | public object[] Values { get { return _values; } } 34 | 35 | private int[] _pIndices; 36 | 37 | private Func _converter; 38 | private Func[] _convertBack; 39 | private object[] _values; 40 | private DataContainer[] _toDataContainers; 41 | private DataContainer[] _fromDataContainers; 42 | private IValueConverter _chainedConverter; 43 | public DynamicMultiConverter(Func converter, Func[] convertBack, object[] values, string convertExp, string convertExpDebug, string[] convertBackExp, string[] convertBackExpDebug, Type[] pTypes, int[] pIndices, Type vType, DataContainer[] toDataContainers, DataContainer[] fromDataContainers, IValueConverter chainedConverter) 44 | { 45 | _converter = converter; 46 | _convertBack = convertBack; 47 | _values = values; 48 | _toDataContainers = toDataContainers; 49 | _fromDataContainers = fromDataContainers; 50 | ConvertExpression = convertExp; 51 | ConvertBackExpression = convertBackExp; 52 | PTypes = pTypes; 53 | this._pIndices = pIndices; 54 | ValueType = vType; 55 | _chainedConverter = chainedConverter; 56 | } 57 | 58 | public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) 59 | { 60 | for (int i = 0; i < values.Length; ++i) 61 | { 62 | if (values[i] == DependencyProperty.UnsetValue || _namedObjectType.IsInstanceOfType(values[i]) || (PTypes[i] != null && !PTypes[i].IsInstanceOfType(values[i]))) 63 | return DependencyProperty.UnsetValue; 64 | } 65 | 66 | object result; 67 | try { result = _converter(values, _values); } 68 | catch (Exception e) 69 | { 70 | LastException = e; 71 | ++ExceptionCount; 72 | if (Debugger.IsAttached) 73 | Console.WriteLine("QuickMultiConverter Exception (\"" + ConvertExpression + "\") - " + e.Message + (e.InnerException != null ? " (Inner - " + e.InnerException.Message + ")" : "")); 74 | EquationTokenizer.ThrowQuickConverterEvent(new RuntimeMultiConvertExceptionEventArgs(ConvertExpression, ConvertExpressionDebugView, values, _pIndices, null, _values, parameter, this, e)); 75 | return DependencyProperty.UnsetValue; 76 | } 77 | finally 78 | { 79 | if (_toDataContainers != null) 80 | { 81 | foreach (var container in _toDataContainers) 82 | container.Value = null; 83 | } 84 | } 85 | 86 | if (result == DependencyProperty.UnsetValue || result == System.Windows.Data.Binding.DoNothing) 87 | return result; 88 | 89 | if (_chainedConverter != null) 90 | { 91 | try { result = _chainedConverter.Convert(result, targetType, parameter, culture); } 92 | catch (Exception e) 93 | { 94 | EquationTokenizer.ThrowQuickConverterEvent(new ChainedConverterExceptionEventArgs(ConvertExpression, result, targetType, parameter, culture, false, _chainedConverter, this, e)); 95 | return DependencyProperty.UnsetValue; 96 | } 97 | } 98 | 99 | result = CastResult(result, targetType); 100 | return result; 101 | } 102 | 103 | public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) 104 | { 105 | if (_chainedConverter != null) 106 | { 107 | try { value = _chainedConverter.ConvertBack(value, typeof(object), parameter, culture); } 108 | catch (Exception e) 109 | { 110 | EquationTokenizer.ThrowQuickConverterEvent(new ChainedConverterExceptionEventArgs(ConvertExpression, value, typeof(object), parameter, culture, true, _chainedConverter, this, e)); 111 | return new object[targetTypes.Length].Select(o => value).ToArray(); 112 | } 113 | 114 | if (value == DependencyProperty.UnsetValue || value == System.Windows.Data.Binding.DoNothing) 115 | return new object[targetTypes.Length].Select(o => value).ToArray(); 116 | } 117 | 118 | object[] ret = new object[_convertBack.Length]; 119 | 120 | if (ValueType != null && !ValueType.IsInstanceOfType(value)) 121 | { 122 | for (int i = 0; i < ret.Length; ++i) 123 | ret[i] = System.Windows.Data.Binding.DoNothing; 124 | return ret; 125 | } 126 | 127 | for (int i = 0; i < _convertBack.Length; ++i) 128 | { 129 | try { ret[i] = _convertBack[i](value, _values); } 130 | catch (Exception e) 131 | { 132 | LastException = e; 133 | ++ExceptionCount; 134 | if (Debugger.IsAttached) 135 | Console.WriteLine("QuickMultiConverter Exception (\"" + ConvertBackExpression[i] + "\") - " + e.Message + (e.InnerException != null ? " (Inner - " + e.InnerException.Message + ")" : "")); 136 | EquationTokenizer.ThrowQuickConverterEvent(new RuntimeMultiConvertExceptionEventArgs(ConvertBackExpression[i], ConvertBackExpressionDebugView[i], null, _pIndices, value, _values, parameter, this, e)); 137 | ret[i] = DependencyProperty.UnsetValue; 138 | } 139 | ret[i] = CastResult(ret[i], targetTypes[i]); 140 | } 141 | if (_fromDataContainers != null) 142 | { 143 | foreach (var container in _fromDataContainers) 144 | container.Value = null; 145 | } 146 | return ret; 147 | } 148 | 149 | private object CastResult(object result, Type targetType) 150 | { 151 | if (result == null || result == DependencyProperty.UnsetValue || result == System.Windows.Data.Binding.DoNothing || targetType == null || targetType == typeof(object)) 152 | return result; 153 | 154 | if (targetType == typeof(string)) 155 | return result.ToString(); 156 | 157 | Func cast; 158 | if (!castFunctions.TryGetValue(targetType, out cast)) 159 | { 160 | ParameterExpression par = Expression.Parameter(typeof(object)); 161 | cast = Expression.Lambda>(Expression.Convert(Expression.Dynamic(Binder.Convert(CSharpBinderFlags.ConvertExplicit, targetType, typeof(object)), targetType, par), typeof(object)), par).Compile(); 162 | castFunctions.TryAdd(targetType, cast); 163 | } 164 | if (cast != null) 165 | { 166 | try 167 | { 168 | result = cast(result); 169 | } 170 | catch 171 | { 172 | castFunctions[targetType] = null; 173 | } 174 | } 175 | return result; 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /QuickEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | using System.Windows.Markup; 11 | using System.Windows.Media; 12 | using QuickConverter; 13 | using Expression = System.Linq.Expressions.Expression; 14 | 15 | namespace QuickConverter 16 | { 17 | public class QuickEvent : MarkupExtension 18 | { 19 | public static object GetP0(DependencyObject obj) { return (object)obj.GetValue(P0Property); } 20 | public static void SetP0(DependencyObject obj, object value) { obj.SetValue(P0Property, value); } 21 | public static readonly DependencyProperty P0Property = DependencyProperty.RegisterAttached("P0", typeof(object), typeof(QuickEvent), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits)); 22 | 23 | public static object GetP1(DependencyObject obj) { return (object)obj.GetValue(P1Property); } 24 | public static void SetP1(DependencyObject obj, object value) { obj.SetValue(P1Property, value); } 25 | public static readonly DependencyProperty P1Property = DependencyProperty.RegisterAttached("P1", typeof(object), typeof(QuickEvent), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits)); 26 | 27 | public static object GetP2(DependencyObject obj) { return (object)obj.GetValue(P2Property); } 28 | public static void SetP2(DependencyObject obj, object value) { obj.SetValue(P2Property, value); } 29 | public static readonly DependencyProperty P2Property = DependencyProperty.RegisterAttached("P2", typeof(object), typeof(QuickEvent), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits)); 30 | 31 | public static object GetP3(DependencyObject obj) { return (object)obj.GetValue(P3Property); } 32 | public static void SetP3(DependencyObject obj, object value) { obj.SetValue(P3Property, value); } 33 | public static readonly DependencyProperty P3Property = DependencyProperty.RegisterAttached("P3", typeof(object), typeof(QuickEvent), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits)); 34 | 35 | public static object GetP4(DependencyObject obj) { return (object)obj.GetValue(P4Property); } 36 | public static void SetP4(DependencyObject obj, object value) { obj.SetValue(P4Property, value); } 37 | public static readonly DependencyProperty P4Property = DependencyProperty.RegisterAttached("P4", typeof(object), typeof(QuickEvent), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits)); 38 | 39 | private static Dictionary> handlers = new Dictionary>(); 40 | 41 | /// 42 | /// The expression to use for handling the event. 43 | /// 44 | public string Handler { get; set; } 45 | 46 | /// 47 | /// This specifies the context to use for dynamic call sites. 48 | /// 49 | public Type DynamicContext { get; set; } 50 | 51 | /// Creates a constant parameter. This can be accessed inside the handler as $V0. 52 | public object V0 { get; set; } 53 | /// Creates a constant parameter. This can be accessed inside the handler as $V1. 54 | public object V1 { get; set; } 55 | /// Creates a constant parameter. This can be accessed inside the handler as $V2. 56 | public object V2 { get; set; } 57 | /// Creates a constant parameter. This can be accessed inside the handler as $V3. 58 | public object V3 { get; set; } 59 | /// Creates a constant parameter. This can be accessed inside the handler as $V4. 60 | public object V4 { get; set; } 61 | /// Creates a constant parameter. This can be accessed inside the handler as $V5. 62 | public object V5 { get; set; } 63 | /// Creates a constant parameter. This can be accessed inside the handler as $V6. 64 | public object V6 { get; set; } 65 | /// Creates a constant parameter. This can be accessed inside the handler as $V7. 66 | public object V7 { get; set; } 67 | /// Creates a constant parameter. This can be accessed inside the handler as $V8. 68 | public object V8 { get; set; } 69 | /// Creates a constant parameter. This can be accessed inside the handler as $V9. 70 | public object V9 { get; set; } 71 | 72 | /// 73 | /// If true, events are bubble up to the target control will be ignored. 74 | /// 75 | public bool IgnoreIfNotOriginalSource { get; set; } 76 | 77 | /// 78 | /// Indicates whether or not to set the event args handled flag to true. 79 | /// 80 | public bool SetHandled { get; set; } 81 | 82 | /// 83 | /// This allows the delegate type to be explicitly instead of inferred from the service provider. 84 | /// 85 | public Type DelegateTypeOverride { get; set; } 86 | 87 | public QuickEvent() 88 | { 89 | SetHandled = true; 90 | } 91 | 92 | public QuickEvent(string handlerExpression) 93 | : this() 94 | { 95 | Handler = handlerExpression; 96 | } 97 | 98 | public override object ProvideValue(IServiceProvider serviceProvider) 99 | { 100 | try 101 | { 102 | if (String.IsNullOrWhiteSpace(Handler)) 103 | throw new Exception("A QuickEvent cannot be created without code for the handler."); 104 | 105 | Type delegateType; 106 | if (DelegateTypeOverride == null) 107 | { 108 | if (serviceProvider is IProvideValueTarget) 109 | { 110 | var prop = (serviceProvider as IProvideValueTarget).TargetProperty; 111 | if (prop is EventInfo) 112 | delegateType = (prop as EventInfo).EventHandlerType; 113 | else if (prop is MethodInfo && (prop as MethodInfo).GetParameters().Length == 2 && typeof(Delegate).IsAssignableFrom((prop as MethodInfo).GetParameters()[1].ParameterType)) 114 | delegateType = (prop as MethodInfo).GetParameters()[1].ParameterType; 115 | else 116 | throw new Exception("QuickEvent must be used on event handlers only."); 117 | } 118 | else 119 | throw new Exception("Either service provider or DelegateTypeOverride must have a value."); 120 | 121 | } 122 | else 123 | delegateType = DelegateTypeOverride; 124 | 125 | var types = delegateType.GetMethod("Invoke").GetParameters().Select(p => p.ParameterType).ToArray(); 126 | if (types.Length != 2) 127 | throw new Exception("QuickEvent only supports event handlers with the standard (sender, eventArgs) signature."); 128 | 129 | var tuple = GetLambda(Handler); 130 | 131 | var handlerType = typeof(QuickEventHandler<,>).MakeGenericType(types); 132 | 133 | var instance = Activator.CreateInstance(handlerType, tuple.Item2, tuple.Item3, new[] { V0, V1, V2, V3, V4, V5, V6, V7, V8, V9 }, Handler, tuple.Item1, tuple.Item4, IgnoreIfNotOriginalSource, SetHandled); 134 | 135 | return Delegate.CreateDelegate(delegateType, instance, "Handle"); 136 | } 137 | catch (Exception e) 138 | { 139 | EquationTokenizer.ThrowQuickConverterEvent(new MarkupExtensionExceptionEventArgs(Handler, this, e)); 140 | throw; 141 | } 142 | } 143 | 144 | private Tuple GetLambda(string expression) 145 | { 146 | Tuple tuple; 147 | if (handlers.TryGetValue(expression, out tuple)) 148 | return tuple; 149 | List parameters; 150 | List dataContainers; 151 | Expression exp = EquationTokenizer.Tokenize(expression, false).GetExpression(out parameters, out dataContainers, DynamicContext, false); 152 | Delegate del = Expression.Lambda(exp, parameters.ToArray()).Compile(); 153 | tuple = new Tuple(exp.ToString(), del, parameters.Select(p => p.Name).ToArray(), dataContainers.ToArray()); 154 | handlers.Add(expression, tuple); 155 | return tuple; 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuickConverter 2 | QuickConverter provides you with WPF markup that allows you to write inline converters, multi-bindings, and event handlers using a C# like language directly in your xaml. 3 | 4 | Even though QuickConverter compiles converter expressions at runtime, its use introduces very little overhead. QuickConverter takes advantage of expression trees and caching to make compilation of dynamically parsed expressions very efficient. 5 | 6 | QuickConverter is also available on NuGet. 7 | 8 | Quick Setup Steps 9 | ----------------- 10 | 11 | 1. Add the assembly reference to your project. This can also easily be done using [the NuGet package](http://www.nuget.org/packages/QuickConverter/). 12 | 2. Add the namespaces to Quick Converter that it will need to know about (before any xaml that uses it is loaded). 13 | e.g. In your WPF application's App.xaml.cs file: 14 | 15 | ```csharp 16 | public partial class App : Application 17 | { 18 | public App() : base() 19 | { 20 | // Setup Quick Converter. 21 | // Add the System namespace so we can use primitive types (i.e. int, etc.). 22 | QuickConverter.EquationTokenizer.AddNamespace(typeof(object)); 23 | // Add the System.Windows namespace so we can use Visibility.Collapsed, etc. 24 | QuickConverter.EquationTokenizer.AddNamespace(typeof(System.Windows.Visibility)); 25 | } 26 | } 27 | ``` 28 | 29 | 3. Add the Quick Converter namespace to your xaml files so you can reference it. 30 | 31 | ``` 32 | xmlns:qc="http://QuickConverter.CodePlex.com/" 33 | ``` 34 | 35 | 36 | More Details 37 | ------------ 38 | 39 | There are two primary classes to know when using of QuickConverter. These are Binding and MultiBinding. These classes can be used in place of System.Windows.Data.Binding and System.Windows.Data.MultiBinding respectively. 40 | 41 | However, before these classes can be used, a small amount of setup is required. Since QuickConverter does static type look ups, the namespaces in which to search need to be specified. For instance, to be able to use 'Visibility.Collapsed' in a converter, you must first register the 'System.Windows' namespace. This only needs to be done once at initialization time. Adding the 'System' namespace from mscorlib can be done using the following call: 42 | 43 | ```csharp 44 | QuickConverter.EquationTokenizer.AddNamespace("System", Assembly.GetAssembly(typeof(object))); 45 | ``` 46 | 47 | In this call you are specifying the namespace and the assembly. This can also be done as follows: 48 | 49 | ```csharp 50 | QuickConverter.EquationTokenizer.AddNamespace(typeof(object)); 51 | ``` 52 | 53 | In this call, the namespace and the assembly are inferred from the type. 54 | 55 | Additionally, for full type names (like "System.Int32"), the assembly it is contained within must be specified. The following shows how to do this: 56 | 57 | ```csharp 58 | QuickConverter.EquationTokenizer.AddAssembly(typeof(object).Assembly); 59 | ``` 60 | 61 | In addition to adding namespaces, extension methods must also be manually registered. To add all the typical LINQ IEnumerable extensions, call the following: 62 | 63 | ```csharp 64 | QuickConverter.EquationTokenizer.AddExtensionMethods(typeof(Enumerable)); 65 | ``` 66 | 67 | **Single Bindings (see QuickConverter Markup section for recommended syntax)** 68 | 69 | QuickConverter.Binding allows you to write a binding with a custom two-way converters in one line of xaml code. 70 | 71 | Here is a binding with a Boolean to System.Visibility converter written with QuickConverter: 72 | 73 | ```xml 74 | 75 | ``` 76 | 77 | Following are two more examples of valid converter code: 78 | 79 | ``` 80 | '$P != null ? $P.GetType() == typeof(int) : false' 81 | ``` 82 | 83 | ``` 84 | '(Double.Parse($P) + 123.0).ToString(\\’0.00\\’)' 85 | ``` 86 | 87 | Constructors and object initializers are also valid: 88 | 89 | ```xml 90 | 91 | ``` 92 | 93 | ```xml 94 | 95 | ``` 96 | 97 | \* Note that generic arguments are enclosed in square brackets instead of angle brackets. Xml doesn't play well with angle brackets. 98 | 99 | The following shows how to write a two-way binding: 100 | 101 | ```xml 102 | 103 | ``` 104 | 105 | **Multi-Bindings** 106 | 107 | Multibinding work in a very similar way. 108 | 109 | The following demonstrates an inline multibinding: 110 | 111 | ```xml 112 | 113 | ``` 114 | 115 | **QuickConverter Markup** 116 | 117 | Converters can also be created independently of the QuickConverter binding extensions. This allows an extra level of flexibility. The following is an example of this: 118 | 119 | ```xml 120 | 121 | ``` 122 | 123 | **Local Variables** 124 | 125 | Local variables can be used through a lambda expression like syntax. Local variables have precedence over binding variables and are only valid with the scope of the lambda expression in which they are declared. The following shows this: 126 | 127 | ```xml 128 | 129 | ``` 130 | 131 | \* Note that the "&&" operator must be written as "&amp;&amp;" in XML. 132 | 133 | \*\* Due to a bug with the designer, using "&amp;" in a markup extension breaks Intellisense. Instead of two ampersands, use the alternate syntax "##". "#" also works for bitwise and operations. 134 | 135 | **Lambda Expressions** 136 | 137 | Support for lambda expressions is limited, but its support is sufficient to allow LINQ to be used. They look quite similar to conventional C# lambda expressions, but there are a few important differences. First off, block expressions are not supported. Only single, inline statements are allowed. Also, the expression must return a value. Additionally, the function will default to object for all generic parameters (eg. Func). This can be overridden with typecast operators. The following shows this: 138 | 139 | ```xml 140 | 141 | ``` 142 | 143 | *Note: The parameters must always be enclosed by parenthesis. 144 | 145 | **Null Propagation Operator** 146 | 147 | The null propagation operator allows safe property/field, method, and indexer access on objects. When used, if the target object is null, instead of throwing an exception null is returned. The operator is implemented by "?".  148 | 149 | Instead of this: 150 | 151 | ``` 152 | '$P != null ? $P.Value : null' 153 | ``` 154 | 155 | You can write this: 156 | 157 | ``` 158 | '$P?.Value' 159 | ``` 160 | 161 | This can be combined with the null coalescing operator to change this: 162 | 163 | ``` 164 | '$P != null ? $P.GetType() == typeof(int) : false' 165 | ``` 166 | 167 | Into this: 168 | 169 | ``` 170 | '($P?.GetType() == typeof(int)) ?? false' 171 | ``` 172 | 173 | This operator is particularly useful in long statements where there are multiple accessors that could throw null exceptions. In this example, we assume Data can never be null when Value is not null. 174 | 175 | ``` 176 | '$P?.Value?.Data.ArrayData?\[4\]' 177 | ``` 178 | 179 | **QuickEvent** 180 | 181 | This markup extension allows you to create event handlers inline. Aside from allowing void functions, the code is identical to QuickConverters. However, QuickEvent exposes a number of variables by default. 182 | 183 | ``` 184 | $sender - The sender of the event. 185 | 186 | $eventArgs - The arguments object of the event. 187 | 188 | $dataContext - The data context of the sender. 189 | 190 | $V0-$V9 - The values set on the QuickEvent Vx properties. 191 | 192 | $P0-$P4 - The values of the QuickEvent.P0-QuickEvent.P4 inherited attached properties on sender. 193 | 194 | ${name} - Any element within the name scope where {name} is the value of x:Name on that element. 195 | ``` 196 | 197 | An example: 198 | 199 | ```xml 200 | 201 |     202 |