/// <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 < 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 ); }