context: //
  // MSBuildErrorParser.cs: Parser for MSBuild-format error messages.
  //
  // Author:
  //   Michael Hutchinson (m.j.hutchinson@gmail.com)
  //
  // Copyright 2014 Xamarin Inc. (http://www.xamarin.com)
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  //
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  
  using System;
  
  namespace Microsoft.Build.Utilities
  {
  	static class MSBuildErrorParser
  	{
  		public class Result
  		{
  			public string Origin { get; set; }
  			public int Line { get; set; }
  			public int Column { get; set; }
  			public int EndLine { get; set; }
  			public int EndColumn { get; set; }
  			public string Subcategory { get; set; }
  			public bool IsError { get; set; }
  			public string Code { get; set; }
  			public string Message { get; set; }
  		}
  
  		// Parses single-line error message in the standard MSBuild error format:
  		//
  		// [origin[(position)]:][subcategory] category code: [message]
  		//
  		// Components in [] square brackets are optional.
  		// Components are as follows:
  		//  origin: tool name or filename, may contain whitespace, no colons except the drive letter
  		//  position: line/col position or range in the file, with one of the following forms:
  		//      (l), (l,c), (l,c-c), (l,c,l,c)
  		//  subcategory: arbitrary text, may contain whitepsace
  		//  code: error code, no whietspace or punctuation
  		//  message: arbitraty text, no restrictions
  		//
  		public static Result TryParseLine (string line)
  		{
  			var result = new Result ();
  
  			int originStart = 0;
  			MoveNextNonSpace (line, ref originStart);
  
  			//find the origin section
  			//the filename may include a colon for Windows drive e.g. C:\foo, so ignore colon in first 2 chars
  			int originEnd = line[originStart] == ':'? originStart : line.IndexOf (':', originStart + 2) - 1;
  			int categoryStart = originEnd + 2;
  			MovePrevNonSpace (line, ref originEnd);
  
  			//if there is no origin section, then we can't parse the message
  			if (originEnd < 0 || originEnd < originStart)
  				return null;
  
  			//find the category section, if there is one
  			MoveNextNonSpace (line, ref categoryStart);
  			int categoryEnd = line.IndexOf (':', categoryStart) - 1;
  			int messageStart = categoryEnd + 2;
  			if (categoryEnd >= 0) {
  				MovePrevNonSpace (line, ref categoryEnd);
  				if (categoryEnd <= categoryStart)
  					categoryEnd = -1;
  			}
  
  			//if there is a category section and it parses
  			if (categoryEnd > 0 && ParseCategory (line, categoryStart, categoryEnd, result)) {
  				//then parse the origin section
  				if (originEnd > originStart && !ParseOrigin (line, originStart, originEnd, result))
  					return null;
  			} else {
  				//there is no origin, parse the origin section as if it were the category
  				if (!ParseCategory (line, originStart, originEnd, result))
  					return null;
  				messageStart = categoryStart;
  			}
  
  			//read the remaining message
  			MoveNextNonSpace (line, ref messageStart);
  			int messageEnd = line.Length - 1;
  			MovePrevNonSpace (line, ref messageEnd, messageStart);
  			if (messageEnd > messageStart) {
  				result.Message = line.Substring (messageStart, messageEnd - messageStart + 1);
  			} else {
  				result.Message = "";
  			}
  
  			return result;
  		}
  
  		// filename (line,col) | tool :
  		static bool ParseOrigin (string line, int start, int end, Result result)
  		{
  			// no line/col
  			if (line [end] != ')') {
  				result.Origin = line.Substring (start, end - start + 1);
  				return true;
  			}
  
  			//scan back for matching (, assuming at least one char between them
  			int posStart = line.LastIndexOf ('(', end - 2, end - start - 2);
  			if (posStart < 0)
  				return false;
  
  			if (!ParsePosition (line, posStart + 1, end, result)) {
  				result.Origin = line.Substring (start, end - start + 1);
  				return true;
  			}
  
  			end = posStart - 1;
  			MovePrevNonSpace (line, ref end, start);
  
  			result.Origin = line.Substring (start, end - start + 1);
  			return true;
  		}
  
  		static bool ParseLineColVal (string str, out int val)
  		{
  			try {
  				val = int.Parse (str);
  				return true;
  			} catch (OverflowException) {
  				val = 0;
  				return true;
  			} catch (FormatException) {
  				val = 0;
  				return false;
  			}
  		}
  
  		// Supported combos:
  		//
  		// (SL,SC,EL,EC)
  		// (SL,SC-EC)
  		// (SL-EL)
  		// (SL,SC)
  		// (SL)
  		//
  		// Unexpected patterns of commas/dashes abort parsing, discarding all values.
  		// Any other characters abort parsing and the (...) gets treated as pert of the filename.
  		// Overflows are silently treated as zeroes.
  		//
  		static bool ParsePosition (string str, int start, int end, Result result)
  		{
  			int line = 0, col = 0, endLine = 0, endCol = 0;
  
  			var a = str.Substring (start, end - start).Split (',');
  
  			if (a.Length > 4 || a.Length == 3)
  				return true;
  
  			if (a.Length == 4) {
  				bool valid =
  					ParseLineColVal (a [0], out line) &&
  					ParseLineColVal (a [1], out col) &&
  					ParseLineColVal (a [2], out endLine) &&
  					ParseLineColVal (a [3], out endCol);
  				if (!valid)
  					return false;
  			} else {
  				var b = a [0].Split ('-');
  				if (b.Length > 2)
  					return true;
  				if (!ParseLineColVal (b [0], out line))
  					return false;
  				if (b.Length == 2) {
  					if (a.Length == 2)
  						return true;
  					if (!ParseLineColVal (b [1], out endLine))
  						return false;
  				}
  				if (a.Length == 2) {
  					var c = a [1].Split ('-');
  					if (c.Length > 2)
  						return true;
  					if (!ParseLineColVal (c [0], out col))
  						return false;
  					if (c.Length == 2) {
  						if (!ParseLineColVal (c [1], out endCol))
  							return false;
  					}
  				}
  			}
  
  			result.Line = line;
  			result.Column = col;
  			result.EndLine = endLine;
  			result.EndColumn = endCol;
  			return true;
  		}
  
  		static bool ParseCategory (string line, int start, int end, Result result)
  		{
  			int idx = end;
  			MovePrevWordStart (line, ref idx, start);
  			if (idx < start + 1)
  				return false;
  
  
gt: 			string code = line.Substring (idx, end - idx + 1);