From ec2590eab76fc208ad18874500017ff3e2c7c7dd Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Mon, 5 Apr 2021 14:33:14 +0200 Subject: [PATCH 1/3] Refactor "Pattern.Match" into stand-alone "PatternMatcher" --- src/DocoptNet/Argument.cs | 11 -- src/DocoptNet/Command.cs | 16 --- src/DocoptNet/Docopt.cs | 2 +- src/DocoptNet/Either.cs | 19 ---- src/DocoptNet/LeafPattern.cs | 40 -------- src/DocoptNet/OneOrMore.cs | 29 ------ src/DocoptNet/Option.cs | 11 -- src/DocoptNet/Optional.cs | 16 --- src/DocoptNet/Pattern.cs | 6 -- src/DocoptNet/PatternMatcher.cs | 150 ++++++++++++++++++++++++++++ src/DocoptNet/Required.cs | 18 ---- tests/DocoptNet.Tests/Extensions.cs | 2 +- 12 files changed, 152 insertions(+), 168 deletions(-) create mode 100644 src/DocoptNet/PatternMatcher.cs diff --git a/src/DocoptNet/Argument.cs b/src/DocoptNet/Argument.cs index 2d493447..4d2bc8d9 100644 --- a/src/DocoptNet/Argument.cs +++ b/src/DocoptNet/Argument.cs @@ -1,7 +1,6 @@ namespace DocoptNet { using System.Collections; - using System.Collections.Generic; class Argument: LeafPattern { @@ -24,16 +23,6 @@ public Argument(string name, int value) { } - public override (int Index, LeafPattern Match) SingleMatch(IList left) - { - for (var i = 0; i < left.Count; i++) - { - if (left[i] is Argument arg) - return (i, new Argument(Name, arg.Value)); - } - return default; - } - public override Node ToNode() { return new ArgumentNode(this.Name, (this.Value != null && this.Value.IsList) ? ValueType.List : ValueType.String); diff --git a/src/DocoptNet/Command.cs b/src/DocoptNet/Command.cs index 3cacba02..db6f05f8 100644 --- a/src/DocoptNet/Command.cs +++ b/src/DocoptNet/Command.cs @@ -1,27 +1,11 @@ namespace DocoptNet { - using System.Collections.Generic; - class Command : Argument { public Command(string name, ValueObject value = null) : base(name, value ?? new ValueObject(false)) { } - public override (int Index, LeafPattern Match) SingleMatch(IList left) - { - for (var i = 0; i < left.Count; i++) - { - if (left[i] is Argument arg) - { - if (arg.Value.ToString() == Name) - return (i, new Command(Name, new ValueObject(true))); - break; - } - } - return default; - } - public override Node ToNode() { return new CommandNode(this.Name); } public override string GenerateCode() diff --git a/src/DocoptNet/Docopt.cs b/src/DocoptNet/Docopt.cs index f131b6d7..cb3716e2 100644 --- a/src/DocoptNet/Docopt.cs +++ b/src/DocoptNet/Docopt.cs @@ -52,7 +52,7 @@ protected IDictionary Apply(string doc, Tokens tokens, optionsShortcut.Children = docOptions.Distinct().Except(patternOptions).ToList(); } Extras(help, version, arguments, doc); - if (pattern.Fix().Match(arguments) is (true, { Count: 0 }, _) res) + if (pattern.Fix().Match(arguments, null) is (true, { Count: 0 }, _) res) { var dict = new Dictionary(); foreach (var p in pattern.Flat().OfType()) diff --git a/src/DocoptNet/Either.cs b/src/DocoptNet/Either.cs index 8f9de7ec..ba98dd33 100644 --- a/src/DocoptNet/Either.cs +++ b/src/DocoptNet/Either.cs @@ -1,28 +1,9 @@ namespace DocoptNet { - using System.Collections.Generic; - using System.Linq; - class Either : BranchPattern { public Either(params Pattern[] patterns) : base(patterns) { } - - public override MatchResult Match(IList left, - IEnumerable collected = null) - { - var coll = collected ?? new List(); - var outcomes = - Children.Select(pattern => pattern.Match(left, coll)) - .Where(outcome => outcome.Matched) - .ToList(); - if (outcomes.Count != 0) - { - var minCount = outcomes.Min(x => x.Left.Count); - return outcomes.First(x => x.Left.Count == minCount); - } - return new MatchResult(false, left, coll); - } } } diff --git a/src/DocoptNet/LeafPattern.cs b/src/DocoptNet/LeafPattern.cs index a69e6ecc..76083391 100644 --- a/src/DocoptNet/LeafPattern.cs +++ b/src/DocoptNet/LeafPattern.cs @@ -38,46 +38,6 @@ public override ICollection Flat(params Type[] types) return new Pattern[] {}; } - public virtual (int Index, LeafPattern Match) SingleMatch(IList patterns) - { - return default; - } - - public override MatchResult Match(IList left, - IEnumerable collected = null) - { - var coll = collected ?? new List(); - var (index, match) = SingleMatch(left); - if (match == null) - { - return new MatchResult(false, left, coll); - } - var left_ = new List(); - left_.AddRange(left.Take(index)); - left_.AddRange(left.Skip(index + 1)); - var sameName = coll.Where(a => a.Name == Name).ToList(); - if (Value != null && (Value.IsList || Value.IsOfTypeInt)) - { - var increment = new ValueObject(1); - if (!Value.IsOfTypeInt) - { - increment = match.Value.IsString ? new ValueObject(new [] {match.Value}) : match.Value; - } - if (sameName.Count == 0) - { - match.Value = increment; - var res = new List(coll) {match}; - return new MatchResult(true, left_, res); - } - sameName[0].Value.Add(increment); - return new MatchResult(true, left_, coll); - } - var resColl = new List(); - resColl.AddRange(coll); - resColl.Add(match); - return new MatchResult(true, left_, resColl); - } - public override string ToString() { return string.Format("{0}({1}, {2})", GetType().Name, Name, Value); diff --git a/src/DocoptNet/OneOrMore.cs b/src/DocoptNet/OneOrMore.cs index e6cb3b73..ba759e11 100644 --- a/src/DocoptNet/OneOrMore.cs +++ b/src/DocoptNet/OneOrMore.cs @@ -1,39 +1,10 @@ namespace DocoptNet { - using System.Collections.Generic; - using System.Diagnostics; - class OneOrMore : BranchPattern { public OneOrMore(params Pattern[] patterns) : base(patterns) { } - - public override MatchResult Match(IList left, - IEnumerable collected = null) - { - Debug.Assert(Children.Count == 1); - var coll = collected ?? new List(); - var l = left; - var c = coll; - IList l_ = null; - var matched = true; - var times = 0; - while (matched) - { - // could it be that something didn't match but changed l or c? - (matched, l, c) = Children[0].Match(l, c); - times += matched ? 1 : 0; - if (l_ != null && l_.Equals(l)) - break; - l_ = l; - } - if (times >= 1) - { - return new MatchResult(true, l, c); - } - return new MatchResult(false, left, coll); - } } } diff --git a/src/DocoptNet/Option.cs b/src/DocoptNet/Option.cs index 9075c653..8d76e039 100644 --- a/src/DocoptNet/Option.cs +++ b/src/DocoptNet/Option.cs @@ -1,7 +1,6 @@ namespace DocoptNet { using System; - using System.Collections.Generic; using System.Text.RegularExpressions; class Option : LeafPattern @@ -48,16 +47,6 @@ public override string GenerateCode() return string.Format("public string {0} {{ get {{ return null == _args[\"{1}\"] ? {2} : _args[\"{1}\"].ToString(); }} }}", s, Name, defaultValue); } - public override (int Index, LeafPattern Match) SingleMatch(IList left) - { - for (var i = 0; i < left.Count; i++) - { - if (left[i].Name == Name) - return (i, left[i]); - } - return default; - } - public override string ToString() { return string.Format("Option({0},{1},{2},{3})", ShortName, LongName, ArgCount, Value); diff --git a/src/DocoptNet/Optional.cs b/src/DocoptNet/Optional.cs index 879cc6fa..bf06781e 100644 --- a/src/DocoptNet/Optional.cs +++ b/src/DocoptNet/Optional.cs @@ -1,26 +1,10 @@ namespace DocoptNet { - using System.Collections.Generic; - class Optional : BranchPattern { public Optional(params Pattern[] patterns) : base(patterns) { } - - public override MatchResult Match(IList left, - IEnumerable collected = null) - { - var c = collected ?? new List(); - var l = left; - foreach (var pattern in Children) - { - var res = pattern.Match(l, c); - l = res.Left; - c = res.Collected; - } - return new MatchResult(true, l, c); - } } } diff --git a/src/DocoptNet/Pattern.cs b/src/DocoptNet/Pattern.cs index a7672235..c7d5f98e 100644 --- a/src/DocoptNet/Pattern.cs +++ b/src/DocoptNet/Pattern.cs @@ -173,12 +173,6 @@ public static Either Transform(Pattern pattern) return new Either(result.Select(r => new Required(r.ToArray()) as Pattern).ToArray()); } - public virtual MatchResult Match(IList left, - IEnumerable collected = null) - { - return new MatchResult(); - } - public abstract ICollection Flat(params Type[] types); /// diff --git a/src/DocoptNet/PatternMatcher.cs b/src/DocoptNet/PatternMatcher.cs new file mode 100644 index 00000000..94340881 --- /dev/null +++ b/src/DocoptNet/PatternMatcher.cs @@ -0,0 +1,150 @@ +namespace DocoptNet +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + + static class PatternMatcher + { + public static MatchResult Match(this Pattern pattern, IList left, IEnumerable collected) + { + switch (pattern) + { + case Required required: + { + var coll = collected ?? new List(); + var l = left; + var c = coll; + foreach (var child in required.Children) + { + bool matched; + (matched, l, c) = child.Match(l, c); + if (!matched) + return new MatchResult(false, left, coll); + } + return new MatchResult(true, l, c); + } + case Either either: + { + var coll = collected ?? new List(); + var outcomes = + either.Children.Select(pattern => Match(pattern, left, coll)) + .Where(outcome => outcome.Matched) + .ToList(); + if (outcomes.Count != 0) + { + var minCount = outcomes.Min(x => x.Left.Count); + return outcomes.First(x => x.Left.Count == minCount); + } + return new MatchResult(false, left, coll); + } + case Optional optional: + { + var c = collected ?? new List(); + var l = left; + foreach (var child in optional.Children) + (_, l, c) = child.Match(l, c); + return new MatchResult(true, l, c); + } + case OneOrMore oneOrMore: + { + Debug.Assert(oneOrMore.Children.Count == 1); + var coll = collected ?? new List(); + var l = left; + var c = coll; + IList l_ = null; + var matched = true; + var times = 0; + while (matched) + { + // could it be that something didn't match but changed l or c? + (matched, l, c) = oneOrMore.Children[0].Match(l, c); + times += matched ? 1 : 0; + if (l_ != null && l_.Equals(l)) + break; + l_ = l; + } + if (times >= 1) + { + return new MatchResult(true, l, c); + } + return new MatchResult(false, left, coll); + } + case LeafPattern leaf: + { + var coll = collected ?? new List(); + var (index, match) = SingleMatch(leaf, left); + if (match == null) + { + return new MatchResult(false, left, coll); + } + var left_ = new List(); + left_.AddRange(left.Take(index)); + left_.AddRange(left.Skip(index + 1)); + var sameName = coll.Where(a => a.Name == leaf.Name).ToList(); + if (leaf.Value != null && (leaf.Value.IsList || leaf.Value.IsOfTypeInt)) + { + var increment = new ValueObject(1); + if (!leaf.Value.IsOfTypeInt) + { + increment = match.Value.IsString ? new ValueObject(new [] {match.Value}) : match.Value; + } + if (sameName.Count == 0) + { + match.Value = increment; + var res = new List(coll) {match}; + return new MatchResult(true, left_, res); + } + sameName[0].Value.Add(increment); + return new MatchResult(true, left_, coll); + } + var resColl = new List(); + resColl.AddRange(coll); + resColl.Add(match); + return new MatchResult(true, left_, resColl); + } + default: throw new ArgumentException(nameof(pattern)); + } + + static (int, LeafPattern) SingleMatch(LeafPattern pattern, IList left) + { + switch (pattern) + { + case Command command: + { + for (var i = 0; i < left.Count; i++) + { + if (left[i] is Argument { Value: { } value }) + { + if (value.ToString() == command.Name) + return (i, new Command(command.Name, new ValueObject(true))); + break; + } + } + return default; + } + case Argument argument: + { + for (var i = 0; i < left.Count; i++) + { + if (left[i] is Argument { Value: var value }) + return (i, new Argument(argument.Name, value)); + } + return default; + } + case Option option: + { + for (var i = 0; i < left.Count; i++) + { + if (left[i].Name == option.Name) + return (i, left[i]); + } + return default; + } + default: throw new ArgumentException(nameof(pattern)); + } + } + } + } +} diff --git a/src/DocoptNet/Required.cs b/src/DocoptNet/Required.cs index d900f98e..a7500ff0 100644 --- a/src/DocoptNet/Required.cs +++ b/src/DocoptNet/Required.cs @@ -1,28 +1,10 @@ namespace DocoptNet { - using System.Collections.Generic; - class Required : BranchPattern { public Required(params Pattern[] patterns) : base(patterns) { } - - public override MatchResult Match(IList left, - IEnumerable collected = null) - { - var coll = collected ?? new List(); - var l = left; - var c = coll; - foreach (var pattern in Children) - { - bool matched; - (matched, l, c) = pattern.Match(l, c); - if (!matched) - return new MatchResult(false, left, coll); - } - return new MatchResult(true, l, c); - } } } diff --git a/tests/DocoptNet.Tests/Extensions.cs b/tests/DocoptNet.Tests/Extensions.cs index 452ba9d3..20489f3d 100644 --- a/tests/DocoptNet.Tests/Extensions.cs +++ b/tests/DocoptNet.Tests/Extensions.cs @@ -4,7 +4,7 @@ static class Extensions { public static MatchResult Match(this Pattern pattern, params LeafPattern[] left) { - return pattern.Match(left); + return pattern.Match(left, null); } } } From 1056a9a274ddec2a9ef52cac4a81641467a95dd0 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Mon, 5 Apr 2021 14:35:57 +0200 Subject: [PATCH 2/3] Move all branch patterns into one file --- .../{BranchPattern.cs => BranchPatterns.cs} | 24 ++++++++++++++++++- src/DocoptNet/Either.cs | 9 ------- src/DocoptNet/OneOrMore.cs | 10 -------- src/DocoptNet/Optional.cs | 10 -------- src/DocoptNet/OptionsShortcut.cs | 12 ---------- src/DocoptNet/Required.cs | 10 -------- 6 files changed, 23 insertions(+), 52 deletions(-) rename src/DocoptNet/{BranchPattern.cs => BranchPatterns.cs} (67%) delete mode 100644 src/DocoptNet/Either.cs delete mode 100644 src/DocoptNet/OneOrMore.cs delete mode 100644 src/DocoptNet/Optional.cs delete mode 100644 src/DocoptNet/OptionsShortcut.cs delete mode 100644 src/DocoptNet/Required.cs diff --git a/src/DocoptNet/BranchPattern.cs b/src/DocoptNet/BranchPatterns.cs similarity index 67% rename from src/DocoptNet/BranchPattern.cs rename to src/DocoptNet/BranchPatterns.cs index 7e5518a1..940575fa 100644 --- a/src/DocoptNet/BranchPattern.cs +++ b/src/DocoptNet/BranchPatterns.cs @@ -9,7 +9,6 @@ namespace DocoptNet /// class BranchPattern : Pattern { - public BranchPattern(params Pattern[] children) { if (children == null) throw new ArgumentNullException(nameof(children)); @@ -38,4 +37,27 @@ public override string ToString() return string.Format("{0}({1})", GetType().Name, string.Join(", ", Children.Select(c => c == null ? "None" : c.ToString()))); } } + + class Required : BranchPattern + { + public Required(params Pattern[] patterns) : base(patterns) { } + } + + class Optional : BranchPattern + { + public Optional(params Pattern[] patterns) : base(patterns) { } + } + + // Marker/placeholder for [options] shortcut. + class OptionsShortcut : Optional { } + + class Either : BranchPattern + { + public Either(params Pattern[] patterns) : base(patterns) { } + } + + class OneOrMore : BranchPattern + { + public OneOrMore(params Pattern[] patterns) : base(patterns) { } + } } diff --git a/src/DocoptNet/Either.cs b/src/DocoptNet/Either.cs deleted file mode 100644 index ba98dd33..00000000 --- a/src/DocoptNet/Either.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace DocoptNet -{ - class Either : BranchPattern - { - public Either(params Pattern[] patterns) : base(patterns) - { - } - } -} diff --git a/src/DocoptNet/OneOrMore.cs b/src/DocoptNet/OneOrMore.cs deleted file mode 100644 index ba759e11..00000000 --- a/src/DocoptNet/OneOrMore.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace DocoptNet -{ - class OneOrMore : BranchPattern - { - public OneOrMore(params Pattern[] patterns) - : base(patterns) - { - } - } -} diff --git a/src/DocoptNet/Optional.cs b/src/DocoptNet/Optional.cs deleted file mode 100644 index bf06781e..00000000 --- a/src/DocoptNet/Optional.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace DocoptNet -{ - class Optional : BranchPattern - { - public Optional(params Pattern[] patterns) : base(patterns) - { - - } - } -} diff --git a/src/DocoptNet/OptionsShortcut.cs b/src/DocoptNet/OptionsShortcut.cs deleted file mode 100644 index 46cb85cc..00000000 --- a/src/DocoptNet/OptionsShortcut.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace DocoptNet -{ - /// - /// Marker/placeholder for [options] shortcut. - /// - class OptionsShortcut : Optional - { - public OptionsShortcut() : base(new Pattern[0]) - { - } - } -} diff --git a/src/DocoptNet/Required.cs b/src/DocoptNet/Required.cs deleted file mode 100644 index a7500ff0..00000000 --- a/src/DocoptNet/Required.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace DocoptNet -{ - class Required : BranchPattern - { - public Required(params Pattern[] patterns) - : base(patterns) - { - } - } -} From f18bf58e94fe3e6dac7580ce16244872007d39d2 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Mon, 5 Apr 2021 14:40:35 +0200 Subject: [PATCH 3/3] Consolidate defaulting of collected leaf patterns list --- src/DocoptNet/PatternMatcher.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/DocoptNet/PatternMatcher.cs b/src/DocoptNet/PatternMatcher.cs index 94340881..909cb904 100644 --- a/src/DocoptNet/PatternMatcher.cs +++ b/src/DocoptNet/PatternMatcher.cs @@ -9,11 +9,12 @@ static class PatternMatcher { public static MatchResult Match(this Pattern pattern, IList left, IEnumerable collected) { + var coll = collected ?? new List(); + switch (pattern) { case Required required: { - var coll = collected ?? new List(); var l = left; var c = coll; foreach (var child in required.Children) @@ -27,7 +28,6 @@ public static MatchResult Match(this Pattern pattern, IList left, I } case Either either: { - var coll = collected ?? new List(); var outcomes = either.Children.Select(pattern => Match(pattern, left, coll)) .Where(outcome => outcome.Matched) @@ -41,8 +41,8 @@ public static MatchResult Match(this Pattern pattern, IList left, I } case Optional optional: { - var c = collected ?? new List(); var l = left; + var c = coll; foreach (var child in optional.Children) (_, l, c) = child.Match(l, c); return new MatchResult(true, l, c); @@ -50,7 +50,6 @@ public static MatchResult Match(this Pattern pattern, IList left, I case OneOrMore oneOrMore: { Debug.Assert(oneOrMore.Children.Count == 1); - var coll = collected ?? new List(); var l = left; var c = coll; IList l_ = null; @@ -73,7 +72,6 @@ public static MatchResult Match(this Pattern pattern, IList left, I } case LeafPattern leaf: { - var coll = collected ?? new List(); var (index, match) = SingleMatch(leaf, left); if (match == null) {