/// <summary>
        /// Converts the given type description to a MySQL type.
        /// </summary>
        /// <param name="typeDescription"></param>
        /// <param name="source">The virtual document the parameter was defined in.</param>
        /// <returns></returns>
        /// <exception cref="GeneratorException">Unable to translate type.</exception>
        public static string ToSQL( string typeDescription, VirtualDocument source )
        {
            switch( typeDescription ) {
            case Keywords.Types.String:
              return "text";

            case Keywords.Types.UnsignedInt:
              return "int(11) unsigned default '0'";

            default:
              // Is this a char[123] type definition?
              if( typeDescription.Substring( 0, Keywords.Types.CharacterArray.Length ) == Keywords.Types.CharacterArray ) {
            // Extract size of character array
            string length = typeDescription.Substring(
              Keywords.Types.CharacterArray.Length + 1,
              typeDescription.Length - ( Keywords.Types.CharacterArray.Length + 2 ) );

            int memberLength = 0;
            if( !int.TryParse( length, out memberLength ) ) {
              throw new GeneratorException( string.Format( "Unable to translate type character '{0}'.", typeDescription ), source );
            }
            return string.Format( "varchar({0}) default '' NOT NULL", memberLength );

              } else {
            throw new GeneratorException( string.Format( "Unable to translate type '{0}'.", typeDescription ), source );
              }

              }
        }
        /// <summary>
        /// Resolves the include statements in the given document.
        /// </summary>
        /// <param name="document"></param>
        /// <returns></returns>
        public static VirtualDocument Resolve( VirtualDocument document )
        {
            // We'll build a dictionary of replacement actions to perform them after initial enumeration
              Dictionary<VirtualDocument.Line, VirtualDocument> substitutions = new Dictionary<VirtualDocument.Line, VirtualDocument>();

              // Try to find lines that start with optional whitespace and then an #include statement
              foreach( VirtualDocument.Line line in document.Lines ) {
            if( line.VirtualLine.Trim().StartsWith( Keywords.PreProcessInclude ) ) {
              // Grab the file name from the statement
              string filename = ExtractFilename( line );
              if( !File.Exists( filename ) ) {
            throw new ParserException( string.Format( "Given include file '{0}' does not exist.", filename ), line );
              }
              Log.InfoFormat( "Including '{0}'.", filename );

              // Remember this substitution
              substitutions.Add( line, VirtualDocument.FromFile( filename ) );
            }
              }

              // Perform substitutions
              foreach( KeyValuePair<VirtualDocument.Line, VirtualDocument> substitution in substitutions ) {
            document.SubstituteLine( substitution.Key, substitution.Value );
              }
              document.UpdateVirtualLineCount();

              return document;
        }
        /// <summary>
        /// Construct a new virtual document from a subsection of another virtual document.
        /// Assumes begin &lt; end
        /// </summary>
        /// <param name="document"></param>
        /// <param name="begin">The character that marks the beginning of the new, virtual document.</param>
        /// <param name="end">The character that marks the end of the new, virtual document.</param>
        /// <returns></returns>
        public static VirtualDocument FromDocument( VirtualDocument document, Character begin, Character end )
        {
            // Store references to lines.
              Line first = begin.ParentLine;
              Line last  = end.ParentLine;

              VirtualDocument section = new VirtualDocument();

              // Find the index of the lines within the document.
              int firstLineIndex = document.Lines.FindIndex( l => l.Characters == first.Characters );
              int lastLineIndex = document.Lines.FindIndex( l => l.Characters == last.Characters );
              Debug.Assert( firstLineIndex != -1 && lastLineIndex != -1 );

              // Clone the lines into the new document
              foreach( Line line in document.Lines.GetRange( firstLineIndex, lastLineIndex - firstLineIndex + 1 ) ) {
            section.Lines.Add( (Line)line.Clone() );
              }
              // Now we can adjust the line numbers for this new virtual document.
              section.UpdateVirtualLineCount();

              // Trim start and end sections
              int startIndex = first.Characters.ToList().IndexOf( begin ) + first.VirtualWindowBegin;
              section.Lines.First().Trim( startIndex );

              int endIndex = last.Characters.ToList().IndexOf( end );
              section.Lines.Last().Trim( ( first != last ) ? 0 : startIndex, endIndex + first.VirtualWindowBegin + 1 );

              return section;
        }
 /// <summary>
 /// Constructs a GeneratorException.
 /// </summary>
 /// <param name="message"></param>
 /// <param name="cause"></param>
 public GeneratorException( string message, VirtualDocument cause )
     : base(message)
 {
     if( !cause.Lines.Any() ) {
     Line = -1;
     File = "<unknown file>";
       } else {
     Line = cause.Lines.First().PhysicalLineIndex;
     File = cause.Lines.First().SourceFile;
       }
 }
        public ParserException( string message, VirtualDocument.Line cause )
            : base(message)
        {
            if( null == cause ) {
            Line = -1;
            File = "<unknown file>";

              } else {
            Line = cause.PhysicalLineIndex + 1;
            File = cause.SourceFile;
              }
        }
        /// <summary>
        /// Translate the given type description to a PHP type.
        /// </summary>
        /// <param name="typeDescription"></param>
        /// <param name="source">The virtual document the parameter was defined in.</param>
        /// <returns></returns>
        /// <exception cref="GeneratorException">Unable to translate type.</exception>
        public static string ToPHP( string typeDescription, VirtualDocument source )
        {
            switch( typeDescription ) {
            case Keywords.Types.String:
              return "string";

            case Keywords.Types.UnsignedInt:
              return "int";

            default:
              // Is this a char[123] type definition?
              if( typeDescription.Substring( 0, Keywords.Types.CharacterArray.Length ) == Keywords.Types.CharacterArray ) {
            return "string";

              } else {
            throw new GeneratorException( string.Format( "Unable to translate type '{0}'.", typeDescription ), source );
              }

              }
        }
        /// <summary>
        /// Grabs the filename from an #include statement.
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        /// <example>#include "foo/bar.txt"</example>
        private static string ExtractFilename( VirtualDocument.Line line )
        {
            string buffer = line.VirtualLine;

              // Remove whitespace
              buffer = buffer.Trim();

              // Remove #include
              buffer = buffer.Substring( Keywords.PreProcessInclude.Length );

              // Consume optional whitespace (between #include and "")
              buffer = buffer.Trim();

              // Remainder is expected to be filename wrapped in ""
              if( !buffer.StartsWith( Syntax.StringDelimiter ) || !buffer.EndsWith( Syntax.StringDelimiter ) ) {
            throw new ParserException( string.Format( "Unterminated string in preprocessor directive '{0}'.", buffer.Trim() ), line );
              }

              // Now we trim those "" away.
              buffer = buffer.Trim( new[] {'"'} );

              // Now we should be left with the filename
              return buffer;
        }
        /// <summary>
        /// Parses a virtual document into a parsed fragment.
        /// </summary>
        /// <param name="document">The virtual document that should be parsed.</param>
        /// <returns></returns>
        public static Fragment ParseFragment( VirtualDocument document )
        {
            DocumentWalker walker = new DocumentWalker( document );

              // The currently collected scope body
              string body = String.Empty;

              // Sub-scopes which we'll find during parsing will be stored in this partial.
              Fragment result = new Fragment {
                                       Body           = string.Empty,
                                       Header         = string.Empty,
                                       SourceDocument = document
                                     };

              // How deeply nested we are into scopes.
              int scopeLevel  = 0;
              // Where the scope we're currently recording started.
              VirtualDocument.Character scopeStart = walker.CurrentCharacter;
              // Are we currently inside a string?
              bool inString = false;
              // Is the current character escaped?
              bool isEscaped = false;
              // Are we currently inside a comment?
              bool inComment = false;
              // The line where a comment was started
              int commentStart = 0;

              // Iterate over the whole input string
              // The whole point of this operation is to collect the full header of the partial,
              // as well as the full body of the partial.
              // If, while parsing the body of the partial, we find nested scopes, we'll store those to parse them later.
              while( walker.CanWalk ) {
            if( !inString ) {
              // Check for scope terminator
              if( walker.CurrentlyReads( Syntax.ScopeTerminate ) ) {
            // Did we find a scope terminator? Like: ;
            if( 1 == scopeLevel ) {
              // As long as we're on the first scope level, we can collect the body of scopes to parse them later.
              // If we're deeper nested, there's no point, we'll parse those when we recurse.

              // Construct a new document for the currently recorded scope.
              VirtualDocument documentFragment = VirtualDocument.FromDocument( document, scopeStart, walker.CurrentCharacter );

              // ...and store it.
              result.Fragments.Add( new Fragment {Body = body.Trim(), SourceDocument = documentFragment} );
              result.Body += walker.CurrentCharacter;

              // Clear buffer
              body = string.Empty;

              // We skip ahead until we see a character again. We need those as markers.
              try {
                walker.WalkToNext();
              } catch( ArgumentOutOfRangeException ) {
                // Things can always go wrong when running!
                break;
              }
              // Set the current location as the new recording start point
              scopeStart = walker.CurrentCharacter;

              continue;
            }
            if( 0 == scopeLevel ) {
              // If we're on the root level, just increase the scope pointer and skip.
              walker.Walk();
              continue;
            }
              }

              // Check for scopes
              if( walker.CurrentlyReads( Syntax.ScopeStart ) ) {
            // Did we find the start of a new scope?
            ++scopeLevel;

            if( 1 == scopeLevel ) {
              // If we're on the root level (we are, because we just increased the scopeLevel), we need to skip ahead.
              walker.Walk();
              scopeStart = walker.CurrentCharacter;
              continue;
            }
            if( 2 == scopeLevel ) {
              //scopeStart = walker.CurrentCharacter;
            }

              } else if( walker.CurrentlyReads( Syntax.ScopeEnd ) ) {
            --scopeLevel;
            // Did we find the end of a scope?
            if( 1 == scopeLevel ) {
              // Great! Another temporary scope we can store for later
              body += walker.CurrentCharacter;
              result.Fragments.Add(
                new Fragment {
                               Body = body.Trim(),
                               SourceDocument =
                                 VirtualDocument.FromDocument( document, scopeStart, walker.CurrentCharacter )
                             } );
              result.Body += walker.CurrentCharacter;
              // Clear buffer
              body = string.Empty;

              // We skip ahead until we see a character again. We need those as markers.
              try {
                walker.WalkToNext();
              } catch( ArgumentOutOfRangeException ) {
                // Things can always go wrong when running!
                break;
              }
              // Set the current location as the new recording start point
              scopeStart = walker.CurrentCharacter;

              continue;
            }
            if( 0 == scopeLevel ) {
              // If we're on the root level, just increase the scope pointer and skip.
              try {
                walker.Walk();
              } catch( ArgumentOutOfRangeException ){}

              continue;
            }
              }

              // Check for string delimiter
              if( walker.CurrentlyReads( Syntax.StringDelimiter ) ) {
            // Did we hit a string delimiter? Like: "
            inString = true;
              }

              // Check for comment
              try {
            //if( characterPointer + Syntax.CommentMultilineStart.Length <= element.Length && Syntax.CommentMultilineStart == element.Substring( characterPointer, Syntax.CommentMultilineStart.Length ) ) {
            if( walker.CurrentlyReads( Syntax.CommentMultilineStart ) ) {

              inComment    = true;
              commentStart = walker.CurrentLine.PhysicalLineIndex;
              // Skip ahead until comment is terminated
              while( walker.CanWalk && inComment ) {
                walker.Walk();
                if( walker.CurrentlyReads( Syntax.CommentMultilineEnd ) ) {
                  walker.Walk( Syntax.CommentMultilineEnd.Length );

                  inComment    = false;
                  commentStart = 0;
                }

              }
            } else if( walker.CurrentlyReads( Syntax.CommentSinglelineStart ) ) {
              // Skip ahead until comment is terminated
              // Single line comments are terminated by newline.
              while( walker.CanWalkForward ) {
                walker.Walk();
              }
              // We walked to the end of the line, no we skip to the next one
              walker.Walk();
              commentStart = 0;

            }
              } catch( ArgumentOutOfRangeException ex ) {
            throw new ParserException( string.Format( "Hit end of input while looking for end of comment which started on line {0}.", commentStart ), walker.Document );
              }

            } else {

              // This is when we're parsing within a string.

              if( walker.CurrentlyReads( Syntax.StringEscape ) ) {
            // Did we find an escape sequence? Like: \"
            isEscaped = true;
            walker.Walk();
              }
              if( !isEscaped && walker.CurrentlyReads( Syntax.StringDelimiter ) ) {
            // Did the string end?
            inString = false;
              }
            }

            // Decide if we're currently parsing a header or a body and store accordingly.
            if( 0 == scopeLevel && walker.CanWalk ) {
              // Store in persitent result
              result.Header += walker.CurrentCharacter;
            } else {
              // Store in persistent result
              result.Body += walker.CurrentCharacter;
              // Also store in temporary buffer
              body += walker.CurrentCharacter;
            }

            isEscaped = false;
            Debug.Assert( walker.CanWalk, "Tried to walk when it's not possible" );
            walker.Walk();
              }

              result.Header = result.Header.Trim();
              result.Body   = result.Body.Trim();

              if( inString ) {
            throw new ParserException( string.Format( "Unmatched \" in {0}", result.Body ), walker.Document );
              }

              // Recurse to resolve all previously parsed fragments
              foreach( Fragment childFragment in result.Fragments ) {
            Fragment fragment = ParseFragment( childFragment.SourceDocument );

            childFragment.Header    = fragment.Header;
            childFragment.Body      = fragment.Body;
            childFragment.Fragments = fragment.Fragments;

            // Pull keyword from header
            int delimiterPosition = childFragment.Header.IndexOf( ' ' );
            if( 0 > delimiterPosition ) delimiterPosition = childFragment.Header.Length;
            childFragment.Keyword = childFragment.Header.Substring( 0, delimiterPosition ).Trim();

            // The remaining part of the header would now be the parameters
            childFragment.Parameters = childFragment.Header.Substring( childFragment.Keyword.Length ).Trim();

            // Is the parameter set in ""?
            if( !string.IsNullOrEmpty( childFragment.Parameters ) && "\"" == childFragment.Parameters.Substring( 0, 1 ) ) {
              if( "\"" != childFragment.Parameters.Substring( childFragment.Parameters.Length -1, 1 ) ) {
            throw new ParserException( string.Format( "Unmatched \" in {0}", childFragment.Header ), walker.Document );
              }
              childFragment.Parameters = childFragment.Parameters.Substring( 1, childFragment.Parameters.Length - 2 );
            }
              }

              return result;
        }
 /// <summary>
 /// Constructs a DocumentWalker for the given virtual document.
 /// </summary>
 /// <param name="document"></param>
 public DocumentWalker( VirtualDocument document )
 {
     Document = document;
 }
        /// <summary>
        /// Construct a new virtual document from the given string.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="sourceFileName">The name of the file this text originated from.</param>
        /// <returns></returns>
        public static VirtualDocument FromText( string text, string sourceFileName = "<memory>" )
        {
            VirtualDocument document = new VirtualDocument();
              document.Lines = Regex.Split( text, "\r\n|\r|\n" ).Select( l => new Line( l, sourceFileName ) ).ToList();
              // Lay down the first
              document.ReIndexLines();
              document.UpdateVirtualLineCount();

              return document;
        }
 /// <summary>
 /// Replaces a given line with another virtual document.
 /// </summary>
 /// <param name="line">The line that should be replaced.</param>
 /// <param name="document">The document that should be inserted.</param>
 public void SubstituteLine( Line line, VirtualDocument document )
 {
     int position = Lines.IndexOf( line );
       Lines.RemoveAt( position );
       Lines.InsertRange( position, document.Lines );
 }