// ==================================================================================================== // YAML Parser for the .NET Framework // ==================================================================================================== // // Copyright (c) 2006 // Christophe Lambrechts // Jonathan Slenders // // ==================================================================================================== // This file is part of the .NET YAML Parser. // // This .NET YAML parser is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation; either version 2.1 of the License, or // (at your option) any later version. // // The .NET YAML parser is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with Foobar; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USAusing System.Reflection; // ==================================================================================================== using System; using System.Collections; using System.IO; namespace Yaml { /// /// The Preprocessor class /// Given a character stream, this class will /// walk through that stream. /// NOTE: Comments are not longer skipped at this level, /// but now in the last level instead. (because of /// problems with comments within the buffer) /// NOTE: Null characters are skipped, read nulls should /// be escaped. \0 /// public class Preprocessor { private TextReader stream; private int currentline = 1; // Line numbers start with one private bool literal = false; // Parse literal/verbatim /// Constuctor public Preprocessor (TextReader stream) { this.stream = stream; } /// Jump to the next character public void Next () { // Transition to the next line? if (Char == '\n') currentline ++; // Not yet passed the end of file if (! EOF) { // Next stream.Read (); // Skip null chars while (stream.Peek () == '\0') stream.Read (); } } /// Start parsing literal public void StartLiteral () { literal = true; } /// Stop parsing literal public void StopLiteral () { if (literal) literal = false; else throw new Exception ("Called StopLiteral without " + "calling StartLiteral before"); } /// Literal parsing public bool Literal { get { return literal; } // No set method, setting must by using the {Start,Stop}Literal // methods. They provide mory symmetry in the parser. } /// The current character public char Char { get { if (EOF) return '\0'; else return (char) stream.Peek (); } } /// End of file/stream public bool EOF { get { return stream.Peek () == -1; } } /// Returns the current line number public int CurrentLine { get { return currentline; } } } /// /// The indentation processor, /// This class divides the stream from the preprocessor /// in substreams, according to the current level /// of indentation. /// public class IndentationProcessor : Preprocessor { // While trying to readahead over whitespaces, // This is how many whitespaces were skipped that weren't yet read private int whitespaces = 0; private int whitespacesSkipped = 0; // Reached the end private bool endofstream = false; // Current level of indentation private int indentationLevel = 0; private bool indentationRequest = false; private Stack indentationStack = new Stack (); /// Constructor public IndentationProcessor (TextReader stream) : base (stream) { } /// /// Request an indentation. When we meet a \n and the following /// line is more indented then the current indentationlever, then /// save this request /// public void Indent () { if (Literal) throw new Exception ("Cannot (un)indent while literal parsing " + "has been enabled"); else { // Handle double requests if (indentationRequest) indentationStack.Push ((object) indentationLevel); // Remember indentationRequest = true; } } /// Cancel the last indentation public void UnIndent () { if (Literal) throw new Exception ("Cannot (un)indent while literal parsing " + "has been enabled"); else { // Cancel the indentation request if (indentationRequest) { indentationRequest = false; return; } // Unpop the last indentation if (indentationStack.Count > 0) indentationLevel = (int) indentationStack.Pop (); // When not indented else throw new Exception ("Unable to unindent a not indented parse stream"); // Parent stream not yet finished // Skipped whitespaces in the childstream (at that time assumed to be // indentation) can become content. if (endofstream && indentationLevel <= whitespaces) { endofstream = false; if (whitespaces == this.indentationLevel) whitespaces = 0; } } } /// Go to the next parsable char in the stream public new void Next () { if (endofstream) return; // Are there still whitespaces to skip if (whitespaces > 0) { // All whitespaces were skipped if (whitespaces == whitespacesSkipped + this.indentationLevel) whitespaces = 0; // Else, skip one else { whitespacesSkipped ++; return; } } // All whitespaces have been skipped if (whitespaces == 0 && ! base.EOF) { // When a char is positioned at a newline '\n', // then skip 'indentation' chars and continue. // When there are less spaces available, then we are // at the end of the (sub)stream if (! base.EOF && base.Char == '\n' && ! Literal) { // Skip over newline base.Next (); // Skip indentation (and count the spaces) int i = 0; while (! base.EOF && base.Char == ' ' && i < this.indentationLevel) { i ++; base.Next (); } // Not enough indented? if (i < this.indentationLevel) { // Remember the number of whitespaces, and // continue at the moment that the indentationlevel // drops below this number of whitespaces whitespaces = i; whitespacesSkipped = 0; endofstream = true; return; } // Indentation request else if (indentationRequest) { while (! base.EOF && base.Char == ' ') { i ++; base.Next (); } // Remember current indentation indentationStack.Push ((object) indentationLevel); indentationRequest = false; // Number of spaces before this line is equal to the // current level of indentation, so the // indentation request cannot be fulfilled if (indentationLevel == i) { whitespaces = i; whitespacesSkipped = 0; endofstream = true; return; } else // i > indentationLevel indentationLevel = i; } } else // Next char base.Next (); } else endofstream = true; } /// Reads the current char from the stream public new char Char { get { // In case of spaces if (whitespaces > 0) return ' '; // \0 at the end of the stream else if (base.EOF || endofstream) return '\0'; // Return the char else return base.Char; } } /// End of File/Stream public new bool EOF { get { return endofstream || base.EOF; } } } /// /// Third stream processor, this class adds a buffer with a maximum /// size of 1024 chars. The buffer cannot encapsulate multiple lines /// because that could do strange things while rewinding/indenting /// public class BufferStream : IndentationProcessor { LookaheadBuffer buffer = new LookaheadBuffer (); // When the buffer is used, this is true private bool useLookaheadBuffer = false; // In use, but requested to destroy. The buffer will keep to exists // (only in this layer) and shall be destroyed when we move out of // the buffer private bool destroyRequest = false; /// Constructor public BufferStream (TextReader stream) : base (stream) { } /// Build lookahead buffer public void BuildLookaheadBuffer () { if (Literal) throw new Exception ("Cannot build a buffer while " + "literal parsing is enabled"); else { // When the buffer is already in use if (useLookaheadBuffer && ! destroyRequest) throw new Exception ("Buffer already exist, cannot rebuild " + "the buffer at this level"); // Cancel the destroy request if (destroyRequest) destroyRequest = false; // Or start a new buffer else { buffer.Clear (); buffer.Append (Char); } useLookaheadBuffer = true; } } /// Move to the next character in the parse stream. public new void Next () { // End of file (This check is not really necessary because base.next // would skip this anyway) if (EOF) return; // When it's not allowed to leave the buffer if (useLookaheadBuffer && ! destroyRequest && ! NextInBuffer () ) return; // When using the lookahead buffer if (useLookaheadBuffer) { // Requested to destroy if (destroyRequest) { // But not yet reached the end of the buffer if (buffer.Position < buffer.LastPosition) { buffer.Position ++; buffer.ForgetThePast (); } // Reached the end else { buffer.Clear (); useLookaheadBuffer = false; destroyRequest = false; base.Next (); } } // Continue in the buffer else { // We've been here before if (buffer.Position < buffer.LastPosition) buffer.Position ++; // This is new to the buffer, but there is place // to remember new chars else if ( buffer.Position == buffer.LastPosition && ! buffer.Full) { // Save the next char in the buffer base.Next(); buffer.Append (base.Char); } // Otherwise, the buffer is full else throw new Exception ("buffer overflow"); } } // Not using the buffer else base.Next(); } /// Returns true when using a buffer public bool UsingBuffer () { return useLookaheadBuffer && ! destroyRequest; } /// /// Returns true when the next char will still be in the buffer /// (after calling next) /// private bool NextInBuffer () { return // Using the buffer useLookaheadBuffer && // Next char has been read before (buffer.Position < buffer.LastPosition || // Or the next char will also be in the buffer (Char != '\n' && ! base.EOF && // There is still unused space ! buffer.Full)); } /// Destroys the current lookaheadbuffer, if there is one public void DestroyLookaheadBuffer () { if (useLookaheadBuffer && ! destroyRequest) { buffer.ForgetThePast (); destroyRequest = true; } else throw new Exception ("Called destroy buffer before building the buffer"); } /// Rewind the buffer public void RewindLookaheadBuffer () { if (! useLookaheadBuffer || destroyRequest) throw new Exception ("Cannot rewind the buffer. No buffer in use"); else buffer.Rewind (); } /// The current character public new char Char { get { // When using a buffer if (useLookaheadBuffer) return buffer.Char; else return base.Char; } } /// End of stream/file public new bool EOF { get { return // When it's not allowed to run out of the buffer (useLookaheadBuffer && ! destroyRequest && ! NextInBuffer () ) || // Not using the buffer, but the end of stream has been reached (! useLookaheadBuffer && base.EOF); } } /// Current position in the lookahead buffer protected int LookaheadPosition { get { if (useLookaheadBuffer) return buffer.Position; else throw new Exception ("Not using a lookahead buffer"); } set { if (useLookaheadBuffer) { if (value >= 0 && value <= buffer.LastPosition) buffer.Position = value; else throw new Exception ("Lookahead position not between 0 " + "and the buffer size"); } else throw new Exception ("Not using a lookahead buffer"); } } } /// Parsestream with multilever buffer public class MultiBufferStream : BufferStream { private Stack bufferStack = new Stack (); // Top is current buffer start /// Constructor public MultiBufferStream (TextReader stream) : base (stream) { } /// Destroy the current buffer public new void BuildLookaheadBuffer () { if (Literal) throw new Exception ("Cannot build a buffer while " + "literal parsing is enabled"); else { // Already using a buffer if (base.UsingBuffer ()) // Remember the current position bufferStack.Push ((object) base.LookaheadPosition); // Otherwise, create a new buffer else { // Remember the current position (= 0) bufferStack .Push ((object) 0); base.BuildLookaheadBuffer (); } } } /// Destroy the current buffer public new void DestroyLookaheadBuffer () { // Clear the buffer info when we runned out of the buffer, if ( ! base.UsingBuffer () ) bufferStack.Clear (); else { // Unpop the buffers start index bufferStack.Pop (); // Destroy it when the last buffer is gone if (bufferStack.Count == 0) base.DestroyLookaheadBuffer (); } } /// Rewind the current buffer public new void RewindLookaheadBuffer () { if (base.UsingBuffer () ) base.LookaheadPosition = (int) bufferStack.Peek (); else throw new Exception ("Rewinding not possible. Not using a " + "lookahead buffer."); } } /// /// Drop the comments /// (This is disabled when literal parsing is enabled) /// public class DropComments : MultiBufferStream { /// Constructor public DropComments (TextReader stream) : base (stream) { } /// Move to the next character in the parse stream. public new void Next () { base.Next (); // Skip comments if (base.Char == '#' && ! Literal) while (! base.EOF && base.Char != '\n') base.Next (); } } /// /// This layer removes the trailing newline at the end of each (sub)stream /// public class DropTrailingNewline : DropComments { // One char buffer private bool newline = false; /// Constructor public DropTrailingNewline (TextReader stream) : base (stream) { } /// The current character public new char Char { get { if (EOF) return '\0'; else if (newline) return '\n'; else return base.Char; } } /// End of File/Stream public new bool EOF { get { return ! newline && base.EOF; } } /// Skip space characters public int SkipSpaces () { int count = 0; while (Char == ' ') { Next (); count ++; } return count; } /// Move to the next character in the parse stream. public new void Next () { Next (false); } /// Move to the next character in the parse stream. /// Forget the last newline public void Next (bool dropLastNewLine) { if (newline) newline = false; else { base.Next (); if (dropLastNewLine && ! base.EOF && Char == '\n') { base.Next (); if (base.EOF) newline = false; else newline = true; } } } } /// /// Stops parsing at specific characters, useful for parsing inline /// structures like (for instance): /// /// [aaa, bbb, ccc, {ddd: eee, "fff": ggg}] /// public class ParseStream : DropTrailingNewline { private Stack stopstack = new Stack (); /// Constructor public ParseStream (TextReader stream) : base (stream) { } /// Set the characters where we should stop. public void StopAt (char [] characters) { stopstack.Push (characters); } /// Unset the characters where we should stop. public void DontStop () { if (stopstack.Count > 0) stopstack.Pop (); else throw new Exception ("Called DontStop without " + "calling StopAt before"); } /// True when we have to stop here private bool StopNow { get { if (stopstack.Count > 0) foreach (char c in (char []) stopstack.Peek ()) if (c == base.Char) return true; return false; } } /// Start parsing literal public new void StartLiteral () { base.StartLiteral (); // Parsing literal disables stopping StopAt (new Char [] { }); } /// Stop parsing literal public new void StopLiteral () { base.StopLiteral (); DontStop (); } /// Move to the next character in the parse stream. public new void Next () { Next (false); } /// Move to the next character in the parse stream. public new void Next (bool dropLastNewLine) { if ( ! StopNow ) base.Next (dropLastNewLine); } /// The current character public new char Char { get { if (StopNow) return '\0'; else return base.Char; } } /// End of stream/file public new bool EOF { get { return StopNow || base.EOF; } } } /// /// The lookahead buffer, used by the buffer layer in the parser /// class LookaheadBuffer { // The buffer array private char [] buffer = new char [1024]; private int size = 0; // 0 = Nothing in the buffer private int position = -1; // Current position private int rotation = 0; // Start of circular buffer /// Character at the current position public char Char { get { if (size > 0) return buffer [(position + rotation) % buffer.Length]; else throw new Exception ("Trying to read from an emty buffer"); } } /// The current position public int Position { get { return position; } set { if (value >= 0 && value < size) position = value; else throw new Exception ("Buffer position should be " + "between zero and 'size' "); } } /// The last possible postition which could be set public int LastPosition { get { return size - 1; } } /// /// The last possible position which could be set if /// the buffer where full /// public int MaxPosition { get { return buffer.Length - 1; } } /// True when the buffer is full public bool Full { get { return size == buffer.Length; } } /// Current buffer size public int Size { get { return size; } } /// Append a character to the buffer public void Append (char c) { // Appending is only possible when the current position is the // last in the buffer if (position < LastPosition) throw new Exception ("Appending to buffer only possible " + "when the position is the last"); // Buffer overflow if (size == buffer.Length) throw new Exception ("Buffer full"); // Append position ++; size ++; buffer [(position + rotation) % buffer.Length] = c; } /// Rewind the buffer public void Rewind () { position = 0; } /// Reset (clear) the buffer public void Clear () { position = -1; size = 0; } /// Move to the next character public void Next () { if (Position < Size) Position ++; else throw new Exception ("Cannot move past the buffer"); } /// /// Remove characters from the buffer before the current character /// public void ForgetThePast () { // Size becomes smaller, characters before the position should be dropped size -= position; // The current position becomes the new startposition rotation = (rotation + position + buffer.Length) % buffer.Length; // The current position in the new buffer becomes zero position = 0; } } }