Init RaspberryIO
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a generic implementation of an Atomic (interlocked) type
|
||||
///
|
||||
/// Idea taken from Memory model and .NET operations in article:
|
||||
/// http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The structure type backed by a 64-bit value.</typeparam>
|
||||
public abstract class AtomicTypeBase<T> : IComparable, IComparable<T>, IComparable<AtomicTypeBase<T>>, IEquatable<T>, IEquatable<AtomicTypeBase<T>>
|
||||
where T : struct, IComparable, IComparable<T>, IEquatable<T>
|
||||
{
|
||||
private long _backingValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AtomicTypeBase{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="initialValue">The initial value.</param>
|
||||
protected AtomicTypeBase(long initialValue)
|
||||
{
|
||||
BackingValue = initialValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value.
|
||||
/// </summary>
|
||||
public T Value
|
||||
{
|
||||
get => FromLong(BackingValue);
|
||||
set => BackingValue = ToLong(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the backing value.
|
||||
/// </summary>
|
||||
protected long BackingValue
|
||||
{
|
||||
get => Interlocked.Read(ref _backingValue);
|
||||
set => Interlocked.Exchange(ref _backingValue, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator ==.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator ==(AtomicTypeBase<T> a, T b) => a?.Equals(b) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator !=.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator !=(AtomicTypeBase<T> a, T b) => a?.Equals(b) == false;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator >.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator >(AtomicTypeBase<T> a, T b) => a.CompareTo(b) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator <.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator <(AtomicTypeBase<T> a, T b) => a.CompareTo(b) < 0;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator >=.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator >=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) >= 0;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator <=.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator <=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) <= 0;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator ++.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static AtomicTypeBase<T> operator ++(AtomicTypeBase<T> instance)
|
||||
{
|
||||
Interlocked.Increment(ref instance._backingValue);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator --.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static AtomicTypeBase<T> operator --(AtomicTypeBase<T> instance)
|
||||
{
|
||||
Interlocked.Decrement(ref instance._backingValue);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator -<.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <param name="operand">The operand.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static AtomicTypeBase<T> operator +(AtomicTypeBase<T> instance, long operand)
|
||||
{
|
||||
instance.BackingValue = instance.BackingValue + operand;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator -.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <param name="operand">The operand.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static AtomicTypeBase<T> operator -(AtomicTypeBase<T> instance, long operand)
|
||||
{
|
||||
instance.BackingValue = instance.BackingValue - operand;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the value to the other instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance.</param>
|
||||
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
|
||||
/// <exception cref="ArgumentException">When types are incompatible.</exception>
|
||||
public int CompareTo(object other)
|
||||
{
|
||||
switch (other)
|
||||
{
|
||||
case null:
|
||||
return 1;
|
||||
case AtomicTypeBase<T> atomic:
|
||||
return BackingValue.CompareTo(atomic.BackingValue);
|
||||
case T variable:
|
||||
return Value.CompareTo(variable);
|
||||
}
|
||||
|
||||
throw new ArgumentException("Incompatible comparison types");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the value to the other instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance.</param>
|
||||
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
|
||||
public int CompareTo(T other) => Value.CompareTo(other);
|
||||
|
||||
/// <summary>
|
||||
/// Compares the value to the other instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance.</param>
|
||||
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
|
||||
public int CompareTo(AtomicTypeBase<T> other) => BackingValue.CompareTo(other?.BackingValue ?? default);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="object" />, is equal to this instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The <see cref="object" /> to compare with this instance.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified <see cref="object" /> is equal to this instance; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
switch (other)
|
||||
{
|
||||
case AtomicTypeBase<T> atomic:
|
||||
return Equals(atomic);
|
||||
case T variable:
|
||||
return Equals(variable);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
|
||||
/// </returns>
|
||||
public override int GetHashCode() => BackingValue.GetHashCode();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(AtomicTypeBase<T> other) =>
|
||||
BackingValue == (other?.BackingValue ?? default);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(T other) => Equals(Value, other);
|
||||
|
||||
/// <summary>
|
||||
/// Converts from a long value to the target type.
|
||||
/// </summary>
|
||||
/// <param name="backingValue">The backing value.</param>
|
||||
/// <returns>The value converted form a long value.</returns>
|
||||
protected abstract T FromLong(long backingValue);
|
||||
|
||||
/// <summary>
|
||||
/// Converts from the target type to a long value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>The value converted to a long value.</returns>
|
||||
protected abstract long ToLong(T value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
/// <summary>
|
||||
/// A threading <see cref="_backingTimer"/> implementation that executes at most one cycle at a time
|
||||
/// in a <see cref="ThreadPool"/> thread. Callback execution is NOT guaranteed to be carried out
|
||||
/// on the same <see cref="ThreadPool"/> thread every time the timer fires.
|
||||
/// </summary>
|
||||
public sealed class ExclusiveTimer : IDisposable
|
||||
{
|
||||
private readonly object _syncLock = new object();
|
||||
private readonly ManualResetEventSlim _cycleDoneEvent = new ManualResetEventSlim(true);
|
||||
private readonly Timer _backingTimer;
|
||||
private readonly TimerCallback _userCallback;
|
||||
private readonly AtomicBoolean _isDisposing = new AtomicBoolean();
|
||||
private readonly AtomicBoolean _isDisposed = new AtomicBoolean();
|
||||
private int _period;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public ExclusiveTimer(TimerCallback timerCallback, object state, int dueTime, int period)
|
||||
{
|
||||
_period = period;
|
||||
_userCallback = timerCallback;
|
||||
_backingTimer = new Timer(InternalCallback, state ?? this, dueTime, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public ExclusiveTimer(TimerCallback timerCallback, object state, TimeSpan dueTime, TimeSpan period)
|
||||
: this(timerCallback, state, Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds))
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
public ExclusiveTimer(TimerCallback timerCallback)
|
||||
: this(timerCallback, null, Timeout.Infinite, Timeout.Infinite)
|
||||
{
|
||||
// placholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public ExclusiveTimer(Action timerCallback, int dueTime, int period)
|
||||
: this(s => { timerCallback?.Invoke(); }, null, dueTime, period)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public ExclusiveTimer(Action timerCallback, TimeSpan dueTime, TimeSpan period)
|
||||
: this(s => { timerCallback?.Invoke(); }, null, dueTime, period)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
public ExclusiveTimer(Action timerCallback)
|
||||
: this(timerCallback, Timeout.Infinite, Timeout.Infinite)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is disposing.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is disposing; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsDisposing => _isDisposing.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is disposed.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsDisposed => _isDisposed.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Changes the start time and the interval between method invocations for the internal timer.
|
||||
/// </summary>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public void Change(int dueTime, int period)
|
||||
{
|
||||
_period = period;
|
||||
|
||||
_backingTimer.Change(dueTime, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the start time and the interval between method invocations for the internal timer.
|
||||
/// </summary>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public void Change(TimeSpan dueTime, TimeSpan period)
|
||||
=> Change(Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds));
|
||||
|
||||
/// <summary>
|
||||
/// Changes the interval between method invocations for the internal timer.
|
||||
/// </summary>
|
||||
/// <param name="period">The period.</param>
|
||||
public void Resume(int period) => Change(0, period);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the interval between method invocations for the internal timer.
|
||||
/// </summary>
|
||||
/// <param name="period">The period.</param>
|
||||
public void Resume(TimeSpan period) => Change(TimeSpan.Zero, period);
|
||||
|
||||
/// <summary>
|
||||
/// Pauses this instance.
|
||||
/// </summary>
|
||||
public void Pause() => Change(Timeout.Infinite, Timeout.Infinite);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (_isDisposed == true || _isDisposing == true)
|
||||
return;
|
||||
|
||||
_isDisposing.Value = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_backingTimer.Dispose();
|
||||
_cycleDoneEvent.Wait();
|
||||
_cycleDoneEvent.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isDisposed.Value = true;
|
||||
_isDisposing.Value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logic that runs every time the timer hits the due time.
|
||||
/// </summary>
|
||||
/// <param name="state">The state.</param>
|
||||
private void InternalCallback(object state)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (IsDisposed || IsDisposing)
|
||||
return;
|
||||
}
|
||||
|
||||
if (_cycleDoneEvent.IsSet == false)
|
||||
return;
|
||||
|
||||
_cycleDoneEvent.Reset();
|
||||
|
||||
try
|
||||
{
|
||||
_userCallback(state);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cycleDoneEvent?.Set();
|
||||
_backingTimer?.Change(_period, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a generic expression parser.
|
||||
/// </summary>
|
||||
public abstract class ExpressionParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves the expression.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of expression result.</typeparam>
|
||||
/// <param name="tokens">The tokens.</param>
|
||||
/// <returns>The representation of the expression parsed.</returns>
|
||||
public virtual T ResolveExpression<T>(IEnumerable<Token> tokens)
|
||||
{
|
||||
var conversion = Expression.Convert(Parse(tokens), typeof(T));
|
||||
return Expression.Lambda<Func<T>>(conversion).Compile()();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified tokens.
|
||||
/// </summary>
|
||||
/// <param name="tokens">The tokens.</param>
|
||||
/// <returns>The final expression.</returns>
|
||||
public virtual Expression Parse(IEnumerable<Token> tokens)
|
||||
{
|
||||
var expressionStack = new List<Stack<Expression>>();
|
||||
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
if (expressionStack.Any() == false)
|
||||
expressionStack.Add(new Stack<Expression>());
|
||||
|
||||
switch (token.Type)
|
||||
{
|
||||
case TokenType.Wall:
|
||||
expressionStack.Add(new Stack<Expression>());
|
||||
break;
|
||||
case TokenType.Number:
|
||||
expressionStack.Last().Push(Expression.Constant(Convert.ToDecimal(token.Value)));
|
||||
break;
|
||||
case TokenType.Variable:
|
||||
ResolveVariable(token.Value, expressionStack.Last());
|
||||
break;
|
||||
case TokenType.String:
|
||||
expressionStack.Last().Push(Expression.Constant(token.Value));
|
||||
break;
|
||||
case TokenType.Operator:
|
||||
ResolveOperator(token.Value, expressionStack.Last());
|
||||
break;
|
||||
case TokenType.Function:
|
||||
ResolveFunction(token.Value, expressionStack.Last());
|
||||
|
||||
if (expressionStack.Count > 1 && expressionStack.Last().Count == 1)
|
||||
{
|
||||
var lastValue = expressionStack.Last().Pop();
|
||||
expressionStack.Remove(expressionStack.Last());
|
||||
expressionStack.Last().Push(lastValue);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return expressionStack.Last().Pop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the variable.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="expressionStack">The expression stack.</param>
|
||||
public abstract void ResolveVariable(string value, Stack<Expression> expressionStack);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the operator.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="expressionStack">The expression stack.</param>
|
||||
public abstract void ResolveOperator(string value, Stack<Expression> expressionStack);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the function.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="expressionStack">The expression stack.</param>
|
||||
public abstract void ResolveFunction(string value, Stack<Expression> expressionStack);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
/// <summary>
|
||||
/// Interface object map.
|
||||
/// </summary>
|
||||
public interface IObjectMap
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the map.
|
||||
/// </summary>
|
||||
Dictionary<PropertyInfo, List<PropertyInfo>> Map { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the source.
|
||||
/// </summary>
|
||||
Type SourceType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the destination.
|
||||
/// </summary>
|
||||
Type DestinationType { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a generic interface for synchronized locking mechanisms.
|
||||
/// </summary>
|
||||
public interface ISyncLocker : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Acquires a writer lock.
|
||||
/// The lock is released when the returned locking object is disposed.
|
||||
/// </summary>
|
||||
/// <returns>A disposable locking object.</returns>
|
||||
IDisposable AcquireWriterLock();
|
||||
|
||||
/// <summary>
|
||||
/// Acquires a reader lock.
|
||||
/// The lock is released when the returned locking object is disposed.
|
||||
/// </summary>
|
||||
/// <returns>A disposable locking object.</returns>
|
||||
IDisposable AcquireReaderLock();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple Validator interface.
|
||||
/// </summary>
|
||||
public interface IValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// The error message.
|
||||
/// </summary>
|
||||
string ErrorMessage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a value is valid.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type.</typeparam>
|
||||
/// <param name="value"> The value.</param>
|
||||
/// <returns>True if it is valid.False if it is not.</returns>
|
||||
bool IsValid<T>(T value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a generalized API for ManualResetEvent and ManualResetEventSlim.
|
||||
/// </summary>
|
||||
/// <seealso cref="IDisposable" />
|
||||
public interface IWaitEvent : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the event is in the completed state.
|
||||
/// </summary>
|
||||
bool IsCompleted { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the Begin method has been called.
|
||||
/// It returns false after the Complete method is called.
|
||||
/// </summary>
|
||||
bool IsInProgress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the underlying handle is not closed and it is still valid.
|
||||
/// </summary>
|
||||
bool IsValid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is disposed.
|
||||
/// </summary>
|
||||
bool IsDisposed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Enters the state in which waiters need to wait.
|
||||
/// All future waiters will block when they call the Wait method.
|
||||
/// </summary>
|
||||
void Begin();
|
||||
|
||||
/// <summary>
|
||||
/// Leaves the state in which waiters need to wait.
|
||||
/// All current waiters will continue.
|
||||
/// </summary>
|
||||
void Complete();
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the event to be completed.
|
||||
/// </summary>
|
||||
void Wait();
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the event to be completed.
|
||||
/// Returns <c>true</c> when there was no timeout. False if the timeout was reached.
|
||||
/// </summary>
|
||||
/// <param name="timeout">The maximum amount of time to wait for.</param>
|
||||
/// <returns><c>true</c> when there was no timeout. <c>false</c> if the timeout was reached.</returns>
|
||||
bool Wait(TimeSpan timeout);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple interface for application workers.
|
||||
/// </summary>
|
||||
public interface IWorker
|
||||
{
|
||||
/// <summary>
|
||||
/// Should start the task immediately and asynchronously.
|
||||
/// </summary>
|
||||
void Start();
|
||||
|
||||
/// <summary>
|
||||
/// Should stop the task immediately and synchronously.
|
||||
/// </summary>
|
||||
void Stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
#if !NETSTANDARD1_3
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Swan;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an background worker abstraction with a life cycle and running at a independent thread.
|
||||
/// </summary>
|
||||
public abstract class RunnerBase
|
||||
{
|
||||
private Thread _worker;
|
||||
private CancellationTokenSource _cancelTokenSource;
|
||||
private ManualResetEvent _workFinished;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RunnerBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="isEnabled">if set to <c>true</c> [is enabled].</param>
|
||||
protected RunnerBase(bool isEnabled)
|
||||
{
|
||||
Name = GetType().Name;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error messages.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The error messages.
|
||||
/// </value>
|
||||
public List<string> ErrorMessages { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is running.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is running; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is enabled.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is enabled; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsEnabled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts this instance.
|
||||
/// </summary>
|
||||
public virtual void Start()
|
||||
{
|
||||
if (IsEnabled == false)
|
||||
return;
|
||||
|
||||
$"Start Requested".Debug(Name);
|
||||
_cancelTokenSource = new CancellationTokenSource();
|
||||
_workFinished = new ManualResetEvent(false);
|
||||
|
||||
_worker = new Thread(() =>
|
||||
{
|
||||
_workFinished.Reset();
|
||||
IsRunning = true;
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
DoBackgroundWork(_cancelTokenSource.Token);
|
||||
}
|
||||
catch (ThreadAbortException)
|
||||
{
|
||||
$"{nameof(ThreadAbortException)} caught.".Warn(Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
$"{ex.GetType()}: {ex.Message}\r\n{ex.StackTrace}".Error(Name);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Cleanup();
|
||||
_workFinished?.Set();
|
||||
IsRunning = false;
|
||||
"Stopped Completely".Debug(Name);
|
||||
}
|
||||
})
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = $"{Name}Thread",
|
||||
};
|
||||
|
||||
_worker.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops this instance.
|
||||
/// </summary>
|
||||
public virtual void Stop()
|
||||
{
|
||||
if (IsEnabled == false || IsRunning == false)
|
||||
return;
|
||||
|
||||
$"Stop Requested".Debug(Name);
|
||||
_cancelTokenSource.Cancel();
|
||||
var waitRetries = 5;
|
||||
while (waitRetries >= 1)
|
||||
{
|
||||
if (_workFinished.WaitOne(250))
|
||||
{
|
||||
waitRetries = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
waitRetries--;
|
||||
}
|
||||
|
||||
if (waitRetries < 0)
|
||||
{
|
||||
"Workbench stopped gracefully".Debug(Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
"Did not respond to stop request. Aborting thread and waiting . . .".Warn(Name);
|
||||
_worker.Abort();
|
||||
|
||||
if (_workFinished.WaitOne(5000) == false)
|
||||
"Waited and no response. Worker might have been left in an inconsistent state.".Error(Name);
|
||||
else
|
||||
"Waited for worker and it finally responded (OK).".Debug(Name);
|
||||
}
|
||||
|
||||
_workFinished.Dispose();
|
||||
_workFinished = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setups this instance.
|
||||
/// </summary>
|
||||
protected virtual void Setup()
|
||||
{
|
||||
// empty
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanups this instance.
|
||||
/// </summary>
|
||||
protected virtual void Cleanup()
|
||||
{
|
||||
// empty
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the background work.
|
||||
/// </summary>
|
||||
/// <param name="ct">The ct.</param>
|
||||
protected abstract void DoBackgroundWork(CancellationToken ct);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,188 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using Formatters;
|
||||
using Reflection;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a provider to save and load settings using a plain JSON file.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following example shows how to save and load settings.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Abstractions;
|
||||
///
|
||||
/// public class Example
|
||||
/// {
|
||||
/// public static void Main()
|
||||
/// {
|
||||
/// // get user from settings
|
||||
/// var user = SettingsProvider<Settings>.Instance.Global.User;
|
||||
///
|
||||
/// // modify the port
|
||||
/// SettingsProvider<Settings>.Instance.Global.Port = 20;
|
||||
///
|
||||
/// // if we want these settings to persist
|
||||
/// SettingsProvider<Settings>.Instance.PersistGlobalSettings();
|
||||
/// }
|
||||
///
|
||||
/// public class Settings
|
||||
/// {
|
||||
/// public int Port { get; set; } = 9696;
|
||||
///
|
||||
/// public string User { get; set; } = "User";
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <typeparam name="T">The type of settings model.</typeparam>
|
||||
public sealed class SettingsProvider<T>
|
||||
: SingletonBase<SettingsProvider<T>>
|
||||
{
|
||||
private readonly object _syncRoot = new object();
|
||||
|
||||
private T _global;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the configuration file path. By default the entry assembly directory is used
|
||||
/// and the filename is 'appsettings.json'.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The configuration file path.
|
||||
/// </value>
|
||||
public string ConfigurationFilePath { get; set; } =
|
||||
#if NETSTANDARD1_3
|
||||
Path.Combine(Runtime.LocalStoragePath, "appsettings.json");
|
||||
#else
|
||||
Path.Combine(Runtime.EntryAssemblyDirectory, "appsettings.json");
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the global settings object.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The global settings object.
|
||||
/// </value>
|
||||
public T Global
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (Equals(_global, default(T)))
|
||||
ReloadGlobalSettings();
|
||||
|
||||
return _global;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the global settings.
|
||||
/// </summary>
|
||||
public void ReloadGlobalSettings()
|
||||
{
|
||||
if (File.Exists(ConfigurationFilePath) == false || File.ReadAllText(ConfigurationFilePath).Length == 0)
|
||||
{
|
||||
ResetGlobalSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_syncRoot)
|
||||
_global = Json.Deserialize<T>(File.ReadAllText(ConfigurationFilePath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Persists the global settings.
|
||||
/// </summary>
|
||||
public void PersistGlobalSettings() => File.WriteAllText(ConfigurationFilePath, Json.Serialize(Global, true));
|
||||
|
||||
/// <summary>
|
||||
/// Updates settings from list.
|
||||
/// </summary>
|
||||
/// <param name="propertyList">The list.</param>
|
||||
/// <returns>
|
||||
/// A list of settings of type ref="ExtendedPropertyInfo".
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">propertyList.</exception>
|
||||
public List<string> RefreshFromList(List<ExtendedPropertyInfo<T>> propertyList)
|
||||
{
|
||||
if (propertyList == null)
|
||||
throw new ArgumentNullException(nameof(propertyList));
|
||||
|
||||
var changedSettings = new List<string>();
|
||||
var globalProps = Runtime.PropertyTypeCache.RetrieveAllProperties<T>();
|
||||
|
||||
foreach (var property in propertyList)
|
||||
{
|
||||
var propertyInfo = globalProps.FirstOrDefault(x => x.Name == property.Property);
|
||||
|
||||
if (propertyInfo == null) continue;
|
||||
|
||||
var originalValue = propertyInfo.GetValue(Global);
|
||||
var isChanged = propertyInfo.PropertyType.IsArray
|
||||
? property.Value is IEnumerable enumerable && propertyInfo.TrySetArray(enumerable.Cast<object>(), Global)
|
||||
: SetValue(property.Value, originalValue, propertyInfo);
|
||||
|
||||
if (!isChanged) continue;
|
||||
|
||||
changedSettings.Add(property.Property);
|
||||
PersistGlobalSettings();
|
||||
}
|
||||
|
||||
return changedSettings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list.
|
||||
/// </summary>
|
||||
/// <returns>A List of ExtendedPropertyInfo of the type T.</returns>
|
||||
public List<ExtendedPropertyInfo<T>> GetList()
|
||||
{
|
||||
var jsonData = Json.Deserialize(Json.Serialize(Global)) as Dictionary<string, object>;
|
||||
|
||||
return jsonData?.Keys
|
||||
.Select(p => new ExtendedPropertyInfo<T>(p) { Value = jsonData[p] })
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the global settings.
|
||||
/// </summary>
|
||||
public void ResetGlobalSettings()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
_global = Activator.CreateInstance<T>();
|
||||
|
||||
PersistGlobalSettings();
|
||||
}
|
||||
|
||||
private bool SetValue(object property, object originalValue, PropertyInfo propertyInfo)
|
||||
{
|
||||
switch (property)
|
||||
{
|
||||
case null when originalValue == null:
|
||||
break;
|
||||
case null:
|
||||
propertyInfo.SetValue(Global, null);
|
||||
return true;
|
||||
default:
|
||||
if (propertyInfo.PropertyType.TryParseBasicType(property, out var propertyValue) &&
|
||||
!propertyValue.Equals(originalValue))
|
||||
{
|
||||
propertyInfo.SetValue(Global, propertyValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a singleton pattern abstract class.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of class.</typeparam>
|
||||
public abstract class SingletonBase<T> : IDisposable
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// The static, singleton instance reference.
|
||||
/// </summary>
|
||||
protected static readonly Lazy<T> LazyInstance = new Lazy<T>(
|
||||
valueFactory: () => Activator.CreateInstance(typeof(T), true) as T,
|
||||
isThreadSafe: true);
|
||||
|
||||
private bool _isDisposing; // To detect redundant calls
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance that this singleton represents.
|
||||
/// If the instance is null, it is constructed and assigned when this member is accessed.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The instance.
|
||||
/// </value>
|
||||
public static T Instance => LazyInstance.Value;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// Call the GC.SuppressFinalize if you override this method and use
|
||||
/// a non-default class finalizer (destructor).
|
||||
/// </summary>
|
||||
/// <param name="disposeManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposeManaged)
|
||||
{
|
||||
if (_isDisposing) return;
|
||||
|
||||
_isDisposing = true;
|
||||
|
||||
// free managed resources
|
||||
if (LazyInstance == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
var disposableInstance = LazyInstance.Value as IDisposable;
|
||||
disposableInstance?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// swallow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a generic tokenizer.
|
||||
/// </summary>
|
||||
public abstract class Tokenizer
|
||||
{
|
||||
private const char PeriodChar = '.';
|
||||
private const char CommaChar = ',';
|
||||
private const char StringQuotedChar = '"';
|
||||
private const char OpenFuncChar = '(';
|
||||
private const char CloseFuncChar = ')';
|
||||
private const char NegativeChar = '-';
|
||||
|
||||
private const string OpenFuncStr = "(";
|
||||
|
||||
private readonly List<Operator> _operators = new List<Operator>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Tokenizer"/> class.
|
||||
/// This constructor will use the following default operators:
|
||||
///
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Operator</term>
|
||||
/// <description>Precedence</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>=</term>
|
||||
/// <description>1</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>!=</term>
|
||||
/// <description>1</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>></term>
|
||||
/// <description>2</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term><</term>
|
||||
/// <description>2</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>>=</term>
|
||||
/// <description>2</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term><=</term>
|
||||
/// <description>2</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>+</term>
|
||||
/// <description>3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>&</term>
|
||||
/// <description>3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>-</term>
|
||||
/// <description>3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>*</term>
|
||||
/// <description>4</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>(backslash)</term>
|
||||
/// <description>4</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>/</term>
|
||||
/// <description>4</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>^</term>
|
||||
/// <description>4</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
protected Tokenizer(string input)
|
||||
{
|
||||
_operators.AddRange(GetDefaultOperators());
|
||||
Tokenize(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Tokenizer" /> class.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <param name="operators">The operators to use.</param>
|
||||
protected Tokenizer(string input, IEnumerable<Operator> operators)
|
||||
{
|
||||
_operators.AddRange(operators);
|
||||
Tokenize(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tokens.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The tokens.
|
||||
/// </value>
|
||||
public List<Token> Tokens { get; } = new List<Token>();
|
||||
|
||||
/// <summary>
|
||||
/// Validates the input and return the start index for tokenizer.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <param name="startIndex">The start index.</param>
|
||||
/// <returns><c>true</c> if the input is valid, otherwise <c>false</c>.</returns>
|
||||
public abstract bool ValidateInput(string input, out int startIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the type of the function or member.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <returns>The token type.</returns>
|
||||
public abstract TokenType ResolveFunctionOrMemberType(string input);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the function or member.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <param name="position">The position.</param>
|
||||
/// <returns><c>true</c> if the input is a valid function or variable, otherwise <c>false</c>.</returns>
|
||||
public virtual bool EvaluateFunctionOrMember(string input, int position) => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default operators.
|
||||
/// </summary>
|
||||
/// <returns>An array with the operators to use for the tokenizer.</returns>
|
||||
public virtual Operator[] GetDefaultOperators() => new[]
|
||||
{
|
||||
new Operator {Name = "=", Precedence = 1},
|
||||
new Operator {Name = "!=", Precedence = 1},
|
||||
new Operator {Name = ">", Precedence = 2},
|
||||
new Operator {Name = "<", Precedence = 2},
|
||||
new Operator {Name = ">=", Precedence = 2},
|
||||
new Operator {Name = "<=", Precedence = 2},
|
||||
new Operator {Name = "+", Precedence = 3},
|
||||
new Operator {Name = "&", Precedence = 3},
|
||||
new Operator {Name = "-", Precedence = 3},
|
||||
new Operator {Name = "*", Precedence = 4},
|
||||
new Operator {Name = "/", Precedence = 4},
|
||||
new Operator {Name = "\\", Precedence = 4},
|
||||
new Operator {Name = "^", Precedence = 4},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Shunting the yard.
|
||||
/// </summary>
|
||||
/// <param name="includeFunctionStopper">if set to <c>true</c> [include function stopper] (Token type <c>Wall</c>).</param>
|
||||
/// <returns>
|
||||
/// Enumerable of the token in in.
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Wrong token
|
||||
/// or
|
||||
/// Mismatched parenthesis.
|
||||
/// </exception>
|
||||
public virtual IEnumerable<Token> ShuntingYard(bool includeFunctionStopper = true)
|
||||
{
|
||||
var stack = new Stack<Token>();
|
||||
|
||||
foreach (var tok in Tokens)
|
||||
{
|
||||
switch (tok.Type)
|
||||
{
|
||||
case TokenType.Number:
|
||||
case TokenType.Variable:
|
||||
case TokenType.String:
|
||||
yield return tok;
|
||||
break;
|
||||
case TokenType.Function:
|
||||
stack.Push(tok);
|
||||
break;
|
||||
case TokenType.Operator:
|
||||
while (stack.Any() && stack.Peek().Type == TokenType.Operator &&
|
||||
CompareOperators(tok.Value, stack.Peek().Value))
|
||||
yield return stack.Pop();
|
||||
|
||||
stack.Push(tok);
|
||||
break;
|
||||
case TokenType.Comma:
|
||||
while (stack.Any() && (stack.Peek().Type != TokenType.Comma &&
|
||||
stack.Peek().Type != TokenType.Parenthesis))
|
||||
yield return stack.Pop();
|
||||
|
||||
break;
|
||||
case TokenType.Parenthesis:
|
||||
if (tok.Value == OpenFuncStr)
|
||||
{
|
||||
if (stack.Any() && stack.Peek().Type == TokenType.Function)
|
||||
{
|
||||
if (includeFunctionStopper)
|
||||
yield return new Token(TokenType.Wall, tok.Value);
|
||||
}
|
||||
|
||||
stack.Push(tok);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (stack.Peek().Value != OpenFuncStr)
|
||||
yield return stack.Pop();
|
||||
|
||||
stack.Pop();
|
||||
|
||||
if (stack.Any() && stack.Peek().Type == TokenType.Function)
|
||||
{
|
||||
yield return stack.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Wrong token");
|
||||
}
|
||||
}
|
||||
|
||||
while (stack.Any())
|
||||
{
|
||||
var tok = stack.Pop();
|
||||
if (tok.Type == TokenType.Parenthesis)
|
||||
throw new InvalidOperationException("Mismatched parenthesis");
|
||||
|
||||
yield return tok;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CompareOperators(Operator op1, Operator op2) => op1.RightAssociative
|
||||
? op1.Precedence < op2.Precedence
|
||||
: op1.Precedence <= op2.Precedence;
|
||||
|
||||
private void Tokenize(string input)
|
||||
{
|
||||
if (!ValidateInput(input, out var startIndex))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = startIndex; i < input.Length; i++)
|
||||
{
|
||||
if (char.IsWhiteSpace(input, i)) continue;
|
||||
|
||||
if (input[i] == CommaChar)
|
||||
{
|
||||
Tokens.Add(new Token(TokenType.Comma, new string(new[] { input[i] })));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input[i] == StringQuotedChar)
|
||||
{
|
||||
i = ExtractString(input, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char.IsLetter(input, i) || EvaluateFunctionOrMember(input, i))
|
||||
{
|
||||
i = ExtractFunctionOrMember(input, i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char.IsNumber(input, i) || (
|
||||
input[i] == NegativeChar &&
|
||||
((Tokens.Any() && Tokens.Last().Type != TokenType.Number) || !Tokens.Any())))
|
||||
{
|
||||
i = ExtractNumber(input, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input[i] == OpenFuncChar ||
|
||||
input[i] == CloseFuncChar)
|
||||
{
|
||||
Tokens.Add(new Token(TokenType.Parenthesis, new string(new[] { input[i] })));
|
||||
continue;
|
||||
}
|
||||
|
||||
i = ExtractOperator(input, i);
|
||||
}
|
||||
}
|
||||
|
||||
private int ExtractData(
|
||||
string input,
|
||||
int i,
|
||||
Func<string, TokenType> tokenTypeEvaluation,
|
||||
Func<char, bool> evaluation,
|
||||
int right = 0,
|
||||
int left = -1)
|
||||
{
|
||||
var charCount = 0;
|
||||
for (var j = i + right; j < input.Length; j++)
|
||||
{
|
||||
if (evaluation(input[j]))
|
||||
break;
|
||||
|
||||
charCount++;
|
||||
}
|
||||
|
||||
// Extract and set the value
|
||||
var value = input.SliceLength(i + right, charCount);
|
||||
Tokens.Add(new Token(tokenTypeEvaluation(value), value));
|
||||
|
||||
i += charCount + left;
|
||||
return i;
|
||||
}
|
||||
|
||||
private int ExtractOperator(string input, int i) =>
|
||||
ExtractData(input, i, x => TokenType.Operator, x => x == OpenFuncChar ||
|
||||
x == CommaChar ||
|
||||
x == PeriodChar ||
|
||||
x == StringQuotedChar ||
|
||||
char.IsWhiteSpace(x) ||
|
||||
char.IsNumber(x));
|
||||
|
||||
private int ExtractFunctionOrMember(string input, int i) =>
|
||||
ExtractData(input, i, ResolveFunctionOrMemberType, x => x == OpenFuncChar ||
|
||||
x == CloseFuncChar ||
|
||||
x == CommaChar ||
|
||||
char.IsWhiteSpace(x));
|
||||
|
||||
private int ExtractNumber(string input, int i) =>
|
||||
ExtractData(input, i, x => TokenType.Number,
|
||||
x => !char.IsNumber(x) && x != PeriodChar && x != NegativeChar);
|
||||
|
||||
private int ExtractString(string input, int i)
|
||||
{
|
||||
var length = ExtractData(input, i, x => TokenType.String, x => x == StringQuotedChar, 1, 1);
|
||||
|
||||
// open string, report issue
|
||||
if (length == input.Length && input[length - 1] != StringQuotedChar)
|
||||
throw new FormatException($"Parser error (Position {i}): Expected '\"' but got '{input[length - 1]}'.");
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private bool CompareOperators(string op1, string op2)
|
||||
=> CompareOperators(GetOperatorOrDefault(op1), GetOperatorOrDefault(op2));
|
||||
|
||||
private Operator GetOperatorOrDefault(string op)
|
||||
=> _operators.FirstOrDefault(x => x.Name == op) ?? new Operator { Name = op, Precedence = 0 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an operator with precedence.
|
||||
/// </summary>
|
||||
public class Operator
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the precedence.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The precedence.
|
||||
/// </value>
|
||||
public int Precedence { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [right associative].
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [right associative]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool RightAssociative { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Token structure.
|
||||
/// </summary>
|
||||
public struct Token
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Token"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
public Token(TokenType type, string value)
|
||||
{
|
||||
Type = type;
|
||||
Value = type == TokenType.Function || type == TokenType.Operator ? value.ToLowerInvariant() : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The type.
|
||||
/// </value>
|
||||
public TokenType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The value.
|
||||
/// </value>
|
||||
public string Value { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enums the token types.
|
||||
/// </summary>
|
||||
public enum TokenType
|
||||
{
|
||||
/// <summary>
|
||||
/// The number
|
||||
/// </summary>
|
||||
Number,
|
||||
|
||||
/// <summary>
|
||||
/// The string
|
||||
/// </summary>
|
||||
String,
|
||||
|
||||
/// <summary>
|
||||
/// The variable
|
||||
/// </summary>
|
||||
Variable,
|
||||
|
||||
/// <summary>
|
||||
/// The function
|
||||
/// </summary>
|
||||
Function,
|
||||
|
||||
/// <summary>
|
||||
/// The parenthesis
|
||||
/// </summary>
|
||||
Parenthesis,
|
||||
|
||||
/// <summary>
|
||||
/// The operator
|
||||
/// </summary>
|
||||
Operator,
|
||||
|
||||
/// <summary>
|
||||
/// The comma
|
||||
/// </summary>
|
||||
Comma,
|
||||
|
||||
/// <summary>
|
||||
/// The wall, used to specified the end of argument list of the following function
|
||||
/// </summary>
|
||||
Wall,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
namespace Unosquare.Swan.Lite.Abstractions
|
||||
{
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// A base class for implementing models that fire notifications when their properties change.
|
||||
/// This class is ideal for implementing MVVM driven UIs.
|
||||
/// </summary>
|
||||
/// <seealso cref="INotifyPropertyChanged" />
|
||||
public abstract class ViewModelBase : INotifyPropertyChanged
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, bool> QueuedNotifications = new ConcurrentDictionary<string, bool>();
|
||||
private readonly bool UseDeferredNotifications;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ViewModelBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="useDeferredNotifications">Set to <c>true</c> to use deferred notifications in the background.</param>
|
||||
protected ViewModelBase(bool useDeferredNotifications)
|
||||
{
|
||||
UseDeferredNotifications = useDeferredNotifications;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ViewModelBase"/> class.
|
||||
/// </summary>
|
||||
protected ViewModelBase()
|
||||
: this(false)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a property value changes.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>Checks if a property already matches a desired value. Sets the property and
|
||||
/// notifies listeners only when necessary.</summary>
|
||||
/// <typeparam name="T">Type of the property.</typeparam>
|
||||
/// <param name="storage">Reference to a property with both getter and setter.</param>
|
||||
/// <param name="value">Desired value for the property.</param>
|
||||
/// <param name="propertyName">Name of the property used to notify listeners. This
|
||||
/// value is optional and can be provided automatically when invoked from compilers that
|
||||
/// support CallerMemberName.</param>
|
||||
/// <param name="notifyAlso">An rray of property names to notify in addition to notifying the changes on the current property name.</param>
|
||||
/// <returns>True if the value was changed, false if the existing value matched the
|
||||
/// desired value.</returns>
|
||||
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "", string[] notifyAlso = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(storage, value))
|
||||
return false;
|
||||
|
||||
storage = value;
|
||||
NotifyPropertyChanged(propertyName, notifyAlso);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies one or more properties changed.
|
||||
/// </summary>
|
||||
/// <param name="propertyNames">The property names.</param>
|
||||
protected void NotifyPropertyChanged(params string[] propertyNames) => NotifyPropertyChanged(null, propertyNames);
|
||||
|
||||
/// <summary>
|
||||
/// Notifies one or more properties changed.
|
||||
/// </summary>
|
||||
/// <param name="mainProperty">The main property.</param>
|
||||
/// <param name="auxiliaryProperties">The auxiliary properties.</param>
|
||||
private void NotifyPropertyChanged(string mainProperty, string[] auxiliaryProperties)
|
||||
{
|
||||
// Queue property notification
|
||||
if (string.IsNullOrWhiteSpace(mainProperty) == false)
|
||||
QueuedNotifications[mainProperty] = true;
|
||||
|
||||
// Set the state for notification properties
|
||||
if (auxiliaryProperties != null)
|
||||
{
|
||||
foreach (var property in auxiliaryProperties)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(property) == false)
|
||||
QueuedNotifications[property] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Depending on operation mode, either fire the notifications in the background
|
||||
// or fire them immediately
|
||||
if (UseDeferredNotifications)
|
||||
Task.Run(() => NotifyQueuedProperties());
|
||||
else
|
||||
NotifyQueuedProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the queued properties and resets the property name to a non-queued stated.
|
||||
/// </summary>
|
||||
private void NotifyQueuedProperties()
|
||||
{
|
||||
// get a snapshot of property names.
|
||||
var propertyNames = QueuedNotifications.Keys.ToArray();
|
||||
|
||||
// Iterate through the properties
|
||||
foreach (var property in propertyNames)
|
||||
{
|
||||
// don't notify if we don't have a change
|
||||
if (!QueuedNotifications[property]) continue;
|
||||
|
||||
// notify and reset queued state to false
|
||||
try { OnPropertyChanged(property); }
|
||||
finally { QueuedNotifications[property] = false; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a property changes its backing value.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property.</param>
|
||||
private void OnPropertyChanged(string propertyName) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName ?? string.Empty));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user