private static IEnumerable <Pattern> ParseShorts(Tokens tokens, ICollection <Option> options) { // shorts ::= '-' ( chars )* [ [ ' ' ] chars ] ; var token = tokens.Move(); Debug.Assert(token.StartsWith("-") && !token.StartsWith("--")); var left = token.TrimStart(new[] { '-' }); var parsed = new List <Pattern>(); while (left != "") { var shortName = "-" + left[0]; left = left.Substring(1); var similar = options.Where(o => o.ShortName == shortName).ToList(); Option option = null; if (similar.Count > 1) { throw tokens.CreateException(string.Format("{0} is specified ambiguously {1} times", shortName, similar.Count)); } if (similar.Count < 1) { option = new Option(shortName, null, 0); options.Add(option); if (tokens.ThrowsInputError) { option = new Option(shortName, null, 0, new ValueObject(true)); } } else { // why is copying necessary here? option = new Option(shortName, similar[0].LongName, similar[0].ArgCount, similar[0].Value); ValueObject value = null; if (option.ArgCount != 0) { if (left == "") { if (tokens.Current() == null || tokens.Current() == "--") { throw tokens.CreateException(shortName + " requires argument"); } value = new ValueObject(tokens.Move()); } else { value = new ValueObject(left); left = ""; } } if (tokens.ThrowsInputError) { option.Value = value ?? new ValueObject(true); } } parsed.Add(option); } return(parsed); }
private static IEnumerable <Pattern> ParseAtom(Tokens tokens, ICollection <Option> options) { // atom ::= '(' expr ')' | '[' expr ']' | 'options' // | long | shorts | argument | command ; var token = tokens.Current(); var result = new List <Pattern>(); switch (token) { case "[": case "(": { tokens.Move(); string matching; if (token == "(") { matching = ")"; result.Add(new Required(ParseExpr(tokens, options).ToArray())); } else { matching = "]"; result.Add(new Optional(ParseExpr(tokens, options).ToArray())); } if (tokens.Move() != matching) { throw tokens.CreateException("unmatched '" + token + "'"); } } break; case "options": tokens.Move(); result.Add(new OptionsShortcut()); break; default: if (token.StartsWith("--") && token != "--") { return(ParseLong(tokens, options)); } if (token.StartsWith("-") && token != "-" && token != "--") { return(ParseShorts(tokens, options)); } if (token.StartsWith("<") && token.EndsWith(">") || token.All(char.IsUpper)) { result.Add(new Argument(tokens.Move())); } else { result.Add(new Command(tokens.Move())); } break; } return(result); }
private static ICollection <Pattern> ParseSeq(Tokens tokens, ICollection <Option> options) { // seq ::= ( atom [ '...' ] )* ; var result = new List <Pattern>(); while (!new[] { null, "]", ")", "|" }.Contains(tokens.Current())) { var atom = ParseAtom(tokens, options); if (tokens.Current() == "...") { result.Add(new OneOrMore(atom.ToArray())); tokens.Move(); return(result); } result.AddRange(atom); } return(result); }
private static IEnumerable <Pattern> ParseExpr(Tokens tokens, ICollection <Option> options) { // expr ::= seq ( '|' seq )* ; var seq = ParseSeq(tokens, options); if (tokens.Current() != "|") { return(seq); } var result = new List <Pattern>(); if (seq.Count() > 1) { result.Add(new Required(seq.ToArray())); } else { result.AddRange(seq); } while (tokens.Current() == "|") { tokens.Move(); seq = ParseSeq(tokens, options); if (seq.Count() > 1) { result.Add(new Required(seq.ToArray())); } else { result.AddRange(seq); } } result = result.Distinct().ToList(); if (result.Count > 1) { return new[] { new Either(result.ToArray()) } } ; return(result); }
/// <summary> /// Parse command-line argument vector. /// </summary> /// <param name="tokens"></param> /// <param name="options"></param> /// <param name="optionsFirst"></param> /// <returns></returns> internal static IList <Pattern> ParseArgv(Tokens tokens, ICollection <Option> options, bool optionsFirst = false) { // If options_first: // argv ::= [ long | shorts ]* [ argument ]* [ '--' [ argument ]* ] ; // else: // argv ::= [ long | shorts | argument ]* [ '--' [ argument ]* ] ; var parsed = new List <Pattern>(); while (tokens.Current() != null) { if (tokens.Current() == "--") { parsed.AddRange(tokens.Select(v => new Argument(null, new ValueObject(v)))); return(parsed); } if (tokens.Current().StartsWith("--")) { parsed.AddRange(ParseLong(tokens, options)); } else if (tokens.Current().StartsWith("-") && tokens.Current() != "-") { parsed.AddRange(ParseShorts(tokens, options)); } else if (optionsFirst) { parsed.AddRange(tokens.Select(v => new Argument(null, new ValueObject(v)))); return(parsed); } else { parsed.Add(new Argument(null, new ValueObject(tokens.Move()))); } } return(parsed); }
private static IEnumerable <Pattern> ParseLong(Tokens tokens, ICollection <Option> options) { // long ::= '--' chars [ ( ' ' | '=' ) chars ] ; var p = new StringPartition(tokens.Move(), "="); var longName = p.LeftString; Debug.Assert(longName.StartsWith("--")); var value = (p.NoSeparatorFound) ? null : new ValueObject(p.RightString); var similar = options.Where(o => o.LongName == longName).ToList(); if (tokens.ThrowsInputError && similar.Count == 0) { // If not exact match similar = options.Where(o => !string.IsNullOrEmpty(o.LongName) && o.LongName.StartsWith(longName)).ToList(); } if (similar.Count > 1) { // Might be simply specified ambiguously 2+ times? throw tokens.CreateException($"{longName} is not a unique prefix: {string.Join(", ", similar.Select(o => o.LongName))}?"); } Option option = null; if (similar.Count < 1) { var argCount = p.Separator == "=" ? 1 : 0; option = new Option(null, longName, argCount); options.Add(option); if (tokens.ThrowsInputError) { option = new Option(null, longName, argCount, argCount != 0 ? value : new ValueObject(true)); } } else { option = new Option(similar[0].ShortName, similar[0].LongName, similar[0].ArgCount, similar[0].Value); if (option.ArgCount == 0) { if (value != null) { throw tokens.CreateException(option.LongName + " must not have an argument"); } } else { if (value == null) { if (tokens.Current() == null || tokens.Current() == "--") { throw tokens.CreateException(option.LongName + " requires an argument"); } value = new ValueObject(tokens.Move()); } } if (tokens.ThrowsInputError) { option.Value = value ?? new ValueObject(true); } } return(new[] { option }); }