// // Mono.VisualC.Interop.Util.IEnumerableTransform.cs: Rule-based transformation for IEnumerable // // Author: // Alexander Corrado (alexander.corrado@gmail.com) // // Copyright (C) 2010 Alexander Corrado // using System; using System.Linq; using System.Collections; using System.Collections.Generic; namespace Mono.VisualC.Interop.Util { public static class IEnumerableTransform { // Transforms an IEnumerable into another by specific rules. public static IEnumerable Transform (this IEnumerable input, params EmitterFunc [] rules) { CloneableEnumerator enumerator = new CloneableEnumerator (input, 0); while (enumerator.MoveNext ()) { InputData inputData = new InputData (input, enumerator); foreach (var rule in rules) { TOut output; if (rule (inputData.NewContext (), out output) == RuleResult.MatchEmit) yield return output; } } } public static IRule And (this IRule previousRules, IRule parentheticalRules) { return new AndRule (previousRules, parentheticalRules); } public static RuleCompound And (this IRule previousRules) { return new RuleCompound ((subsequentRules) => { return And (previousRules, subsequentRules); }); } public static IRule Or (this IRule previousRules, IRule parentheticalRules) { return new OrRule (previousRules, parentheticalRules); } public static RuleCompound Or (this IRule previousRules) { return new RuleCompound ((subsequentRules) => { return Or (previousRules, subsequentRules); }); } public static IRule After (this IRule previousRules, IRule parentheticalRules) { return new AndRule (new AfterRule (parentheticalRules), previousRules); } public static RuleCompound After (this IRule previousRules) { return new RuleCompound ((subsequentRules) => { return After (previousRules, subsequentRules); }); } public static IRule AtEnd (this IRule previousRules) { return new AtEndRule (previousRules); } public static EmitterFunc Emit (this IRule rule, Func result) { return delegate (InputData input, out TOut output) { output = default (TOut); TIn value = input.Value; RuleResult ruleResult = rule.SatisfiedBy (input); if (ruleResult != RuleResult.NoMatch) { input.MatchedRules.Add (rule); output = result (value); return ruleResult; } return RuleResult.NoMatch; }; } public static EmitterFunc Emit (this IRule rule, TOut result) { return Emit (rule, (input) => result); } // helpers: public static IEnumerable With (this IEnumerable current, T additionalValue) { foreach (var output in current) yield return output; yield return additionalValue; } public static bool StartsWith (this IEnumerable current, IEnumerable beginning) { IEnumerator currentEnum = current.GetEnumerator (); IEnumerator beginEnum = beginning.GetEnumerator (); while (currentEnum.MoveNext ()) { if (!beginEnum.MoveNext ()) return true; if (!currentEnum.Current.Equals (beginEnum.Current)) return false; } return !beginEnum.MoveNext (); } public static IEnumerable Without (this IEnumerable current, T unwantedValue) { foreach (var output in current) { if (!output.Equals (unwantedValue)) yield return output; } } public static IEnumerable WithoutFirst (this IEnumerable current, T unwantedValue) { bool first = true; foreach (var output in current) { if (first && output.Equals (unwantedValue)) first = false; else yield return output; } } public static int SequenceHashCode (this IEnumerable sequence) { int hash = 0; foreach (var item in sequence) hash ^= item.GetHashCode (); return hash; } // FIXME: Faster way to do this? public static void AddFirst (this List list, IEnumerable items) { T [] temp = new T [list.Count]; list.CopyTo (temp, 0); list.Clear (); list.AddRange (items); list.AddRange (temp); } } public enum RuleResult { NoMatch, MatchEmit, MatchNoEmit } public delegate RuleResult EmitterFunc (InputData input, out TOut output); public struct InputData { public IEnumerable AllValues; public CloneableEnumerator Enumerator; public List> MatchedRules; public InputData (IEnumerable input, CloneableEnumerator enumerator) { this.AllValues = input; this.Enumerator = enumerator; this.MatchedRules = new List> (); } public InputData NewContext () { InputData nctx = new InputData (); nctx.AllValues = this.AllValues; nctx.Enumerator = this.Enumerator.Clone (); nctx.MatchedRules = this.MatchedRules; return nctx; } public TIn Value { get { return Enumerator.Current; } } } #region Rules public interface IRule { RuleResult SatisfiedBy (InputData input); } // yields all inputs indescriminately public class AnyRule : IRule { public AnyRule () { } public RuleResult SatisfiedBy (InputData input) { return RuleResult.MatchEmit; } } // yields all inputs that haven't satisfied // any other rule public class UnmatchedRule : IRule { public UnmatchedRule () { } public RuleResult SatisfiedBy (InputData input) { return !input.MatchedRules.Any ()? RuleResult.MatchEmit : RuleResult.NoMatch; } } // yields input if it hasn't been satisfied before public class FirstRule : IRule { protected bool triggered = false; public FirstRule () { } public RuleResult SatisfiedBy (InputData input) { if (!triggered) { triggered = true; return RuleResult.MatchEmit; } return RuleResult.NoMatch; } } // yields input if it is the last that would satisfy // all its matched rules public class LastRule : IRule { public LastRule () { } public RuleResult SatisfiedBy (InputData input) { throw new NotImplementedException (); } } // yields input if the specified previous rule has already been satisfied public class AfterRule : IRule { protected IRule previousRule; protected bool satisfied = false; public AfterRule (IRule previousRule) { this.previousRule = previousRule; } public RuleResult SatisfiedBy (InputData input) { if (satisfied) return RuleResult.MatchEmit; satisfied = previousRule.SatisfiedBy (input) != RuleResult.NoMatch; return RuleResult.NoMatch; } } // yields all inputs found in the specified set public class InSetRule : IRule { protected IEnumerable conditions; public InSetRule (params TIn [] conditions) { this.conditions = conditions; } public InSetRule (IEnumerable conditions) { this.conditions = conditions; } public RuleResult SatisfiedBy (InputData input) { return conditions.Contains (input.Value)? RuleResult.MatchEmit : RuleResult.NoMatch; } } public class PredicateRule : IRule { protected Func predicate; public PredicateRule (Func predicate) { this.predicate = predicate; } public RuleResult SatisfiedBy (InputData input) { return predicate (input.Value)? RuleResult.MatchEmit : RuleResult.NoMatch; } } // is satisfied by any of the specified items as long the // item appears adjacent with all the other items public class AnyOrderRule : IRule { protected IEnumerable conditions; protected Queue verifiedItems = new Queue (); protected bool verified = false; public AnyOrderRule (params TIn [] conditions) { this.conditions = conditions; } public AnyOrderRule (IEnumerable conditions) { this.conditions = conditions; } public RuleResult SatisfiedBy (InputData inputData) { if (verified && verifiedItems.Count > 0) { verifiedItems.Dequeue (); return RuleResult.MatchNoEmit; } else verified = false; IEnumerator input = inputData.Enumerator; while (conditions.Contains (input.Current) && !verifiedItems.Contains (input.Current)) { verifiedItems.Enqueue (input.Current); if (!input.MoveNext ()) break; } if (verifiedItems.Count == conditions.Count ()) { verified = true; verifiedItems.Dequeue (); return RuleResult.MatchEmit; } else verifiedItems.Clear (); return RuleResult.NoMatch; } } public class InOrderRule : IRule { protected IEnumerable conditions; protected bool verified = false; protected int verifiedCount = 0; public InOrderRule (params TIn [] conditions) { this.conditions = conditions; } public InOrderRule (IEnumerable conditions) { this.conditions = conditions; } public RuleResult SatisfiedBy (InputData inputData) { if (verified && verifiedCount > 0) { verifiedCount--; return RuleResult.MatchNoEmit; } else verified = false; IEnumerator condition = conditions.GetEnumerator (); IEnumerator input = inputData.Enumerator; while (condition.MoveNext () && condition.Equals (input.Current)) { verifiedCount++; if (!input.MoveNext ()) break; } if (verifiedCount == conditions.Count ()) { verified = true; verifiedCount--; return RuleResult.MatchEmit; } else verifiedCount = 0; return RuleResult.NoMatch; } } // yields all inputs that match all specified rules public class AndRule : IRule { protected IEnumerable> rules; public AndRule (IEnumerable> rules) { this.rules = rules; } public AndRule (params IRule[] rules) { this.rules = rules; } public RuleResult SatisfiedBy (InputData input) { RuleResult finalResult = RuleResult.NoMatch; foreach (var rule in rules) { RuleResult result = rule.SatisfiedBy (input.NewContext ()); if (result == RuleResult.NoMatch) return RuleResult.NoMatch; if (finalResult != RuleResult.MatchNoEmit) finalResult = result; input.MatchedRules.Add (rule); } return finalResult; } } // yields all inputs that match any specified rules public class OrRule : IRule { protected IEnumerable> rules; public OrRule (IEnumerable> rules) { this.rules = rules; } public OrRule (params IRule[] rules) { this.rules = rules; } public RuleResult SatisfiedBy (InputData input) { foreach (var rule in rules) { RuleResult result = rule.SatisfiedBy (input.NewContext ()); if (result != RuleResult.NoMatch) { input.MatchedRules.Add (rule); return result; } } return RuleResult.NoMatch; } } public class AtEndRule : IRule { protected IRule rule; public AtEndRule (IRule rule) { this.rule = rule; } public RuleResult SatisfiedBy (InputData input) { RuleResult rr = rule.SatisfiedBy (input); if (!input.Enumerator.MoveNext ()) return rr; return RuleResult.NoMatch; } } #endregion // the base point for building up rules // All rules start For... public static class For { public static IRule AnyInputIn (params TIn [] input) { return new InSetRule (input); } public static IRule AnyInputIn (IEnumerable input) { return new InSetRule (input); } public static SequenceQualifier AllInputsIn (params TIn [] input) { return new SequenceQualifier (input, null); } public static SequenceQualifier AllInputsIn (IEnumerable input) { return new SequenceQualifier (input, null); } public static RuleCompound First () { return new RuleCompound ((subsequentRules) => { return new AndRule (subsequentRules, new FirstRule ()); }); } public static RuleCompound Last () { return new RuleCompound ((subsequentRules) => { return new AndRule (subsequentRules, new LastRule ()); }); } public static IRule InputsWhere (Func predicate) { return new PredicateRule (predicate); } public static IRule AnyInput () { return new AnyRule (); } public static IRule UnmatchedInput () { return new UnmatchedRule (); } } public class RuleCompound { protected Func,IRule> additionalRuleCallback; public RuleCompound (Func,IRule> additionalRuleCallback) { this.additionalRuleCallback = additionalRuleCallback; } public SequenceQualifier AllInputsIn (params TIn [] input) { return new SequenceQualifier (input, additionalRuleCallback); } public SequenceQualifier AllInputsIn (IEnumerable input) { return new SequenceQualifier (input, additionalRuleCallback); } public IRule AnyInputIn (params TIn[] input) { return additionalRuleCallback (new InSetRule (input)); } public IRule AnyInputIn (IEnumerable input) { return additionalRuleCallback (new InSetRule (input)); } public IRule InputsWhere (Func predicate) { return additionalRuleCallback (new PredicateRule (predicate)); } public IRule AnyInput () { return additionalRuleCallback (new AnyRule ()); } } public class SequenceQualifier { protected IEnumerable sequence; protected Func, IRule> additionalRuleCallback; public SequenceQualifier (IEnumerable sequence, Func, IRule> additionalRuleCallback) { this.sequence = sequence; this.additionalRuleCallback = additionalRuleCallback ?? (rul => rul); } public IRule InThatOrder () { return additionalRuleCallback (new InOrderRule (sequence)); } public IRule InAnyOrder () { return additionalRuleCallback (new AnyOrderRule (sequence)); } } public static class Choose { public static EmitterFunc TopOne (params EmitterFunc [] rules) { return delegate (InputData input, out TOut output) { output = default (TOut); foreach (var rule in rules) { RuleResult result = rule (input.NewContext (), out output); if (result != RuleResult.NoMatch) return result; } return RuleResult.NoMatch; }; } } public class CloneableEnumerator : IEnumerator { private IEnumerable wrappedEnumerable; private IEnumerator wrappedEnumerator; private int position; public CloneableEnumerator (IEnumerable enumerable, int position) { this.wrappedEnumerable = enumerable; this.position = position; this.wrappedEnumerator = wrappedEnumerable.GetEnumerator (); for (int i = 0; i < position; i++) wrappedEnumerator.MoveNext (); } public CloneableEnumerator Clone () { return new CloneableEnumerator (this.wrappedEnumerable, this.position); } public void Reset () { wrappedEnumerator.Reset (); } public bool MoveNext () { position++; return wrappedEnumerator.MoveNext (); } public T Current { get { return wrappedEnumerator.Current; } } object IEnumerator.Current { get { return this.Current; } } public void Dispose () { } } }