Files
OpenRA/thirdparty/yaml/ParseStream.cs
2010-01-09 19:03:25 +13:00

900 lines
22 KiB
C#

// ====================================================================================================
// 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
{
/// <summary>
/// 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
/// </summary>
public class Preprocessor
{
private TextReader stream;
private int currentline = 1; // Line numbers start with one
private bool literal = false; // Parse literal/verbatim
/// <summary> Constuctor </summary>
public Preprocessor (TextReader stream)
{
this.stream = stream;
}
/// <summary> Jump to the next character </summary>
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 ();
}
}
/// <summary> Start parsing literal </summary>
public void StartLiteral ()
{
literal = true;
}
/// <summary> Stop parsing literal </summary>
public void StopLiteral ()
{
if (literal)
literal = false;
else
throw new Exception ("Called StopLiteral without " +
"calling StartLiteral before");
}
/// <summary> Literal parsing </summary>
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.
}
/// <summary> The current character </summary>
public char Char
{
get
{
if (EOF)
return '\0';
else
return (char) stream.Peek ();
}
}
/// <summary> End of file/stream </summary>
public bool EOF
{
get { return stream.Peek () == -1; }
}
/// <summary> Returns the current line number </summary>
public int CurrentLine
{
get { return currentline; }
}
}
/// <summary>
/// The indentation processor,
/// This class divides the stream from the preprocessor
/// in substreams, according to the current level
/// of indentation.
/// </summary>
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 ();
/// <summary> Constructor </summary>
public IndentationProcessor (TextReader stream) : base (stream) { }
/// <summary>
/// Request an indentation. When we meet a \n and the following
/// line is more indented then the current indentationlever, then
/// save this request
/// </summary>
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;
}
}
/// <summary> Cancel the last indentation </summary>
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;
}
}
}
/// <summary> Go to the next parsable char in the stream </summary>
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;
}
/// <summary> Reads the current char from the stream </summary>
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;
}
}
/// <summary> End of File/Stream </summary>
public new bool EOF
{
get { return endofstream || base.EOF; }
}
}
/// <summary>
/// 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
/// </summary>
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;
/// <summary> Constructor </summary>
public BufferStream (TextReader stream) : base (stream) { }
/// <summary> Build lookahead buffer </summary>
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;
}
}
/// <summary> Move to the next character in the parse stream. </summary>
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();
}
/// <summary> Returns true when using a buffer </summary>
public bool UsingBuffer ()
{
return useLookaheadBuffer && ! destroyRequest;
}
/// <summary>
/// Returns true when the next char will still be in the buffer
/// (after calling next)
/// </summary>
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));
}
/// <summary> Destroys the current lookaheadbuffer, if there is one </summary>
public void DestroyLookaheadBuffer ()
{
if (useLookaheadBuffer && ! destroyRequest)
{
buffer.ForgetThePast ();
destroyRequest = true;
}
else
throw new Exception ("Called destroy buffer before building the buffer");
}
/// <summary> Rewind the buffer </summary>
public void RewindLookaheadBuffer ()
{
if (! useLookaheadBuffer || destroyRequest)
throw new Exception ("Cannot rewind the buffer. No buffer in use");
else
buffer.Rewind ();
}
/// <summary> The current character </summary>
public new char Char
{
get
{
// When using a buffer
if (useLookaheadBuffer)
return buffer.Char;
else
return base.Char;
}
}
/// <summary> End of stream/file </summary>
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);
}
}
/// <summary> Current position in the lookahead buffer </summary>
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");
}
}
}
/// <summary> Parsestream with multilever buffer </summary>
public class MultiBufferStream : BufferStream
{
private Stack bufferStack = new Stack (); // Top is current buffer start
/// <summary> Constructor </summary>
public MultiBufferStream (TextReader stream) : base (stream) { }
/// <summary> Destroy the current buffer </summary>
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 ();
}
}
}
/// <summary> Destroy the current buffer </summary>
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 ();
}
}
/// <summary> Rewind the current buffer </summary>
public new void RewindLookaheadBuffer ()
{
if (base.UsingBuffer () )
base.LookaheadPosition = (int) bufferStack.Peek ();
else
throw new Exception ("Rewinding not possible. Not using a " +
"lookahead buffer.");
}
}
/// <summary>
/// Drop the comments
/// (This is disabled when literal parsing is enabled)
/// </summary>
public class DropComments : MultiBufferStream
{
/// <summary> Constructor </summary>
public DropComments (TextReader stream) : base (stream) { }
/// <summary> Move to the next character in the parse stream. </summary>
public new void Next ()
{
base.Next ();
// Skip comments
if (base.Char == '#' && ! Literal)
while (! base.EOF && base.Char != '\n')
base.Next ();
}
}
/// <summary>
/// This layer removes the trailing newline at the end of each (sub)stream
/// </summary>
public class DropTrailingNewline : DropComments
{
// One char buffer
private bool newline = false;
/// <summary> Constructor </summary>
public DropTrailingNewline (TextReader stream) : base (stream) { }
/// <summary> The current character </summary>
public new char Char
{
get
{
if (EOF)
return '\0';
else if (newline)
return '\n';
else
return base.Char;
}
}
/// <summary> End of File/Stream </summary>
public new bool EOF
{
get { return ! newline && base.EOF; }
}
/// <summary> Skip space characters </summary>
public int SkipSpaces ()
{
int count = 0;
while (Char == ' ')
{
Next ();
count ++;
}
return count;
}
/// <summary> Move to the next character in the parse stream. </summary>
public new void Next ()
{
Next (false);
}
/// <summary> Move to the next character in the parse stream. </summary>
/// <param name="dropLastNewLine"> Forget the last newline </param>
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;
}
}
}
}
/// <summary>
/// Stops parsing at specific characters, useful for parsing inline
/// structures like (for instance):
///
/// [aaa, bbb, ccc, {ddd: eee, "fff": ggg}]
/// </summary>
public class ParseStream : DropTrailingNewline
{
private Stack stopstack = new Stack ();
/// <summary> Constructor </summary>
public ParseStream (TextReader stream) : base (stream) { }
/// <summary> Set the characters where we should stop. </summary>
public void StopAt (char [] characters)
{
stopstack.Push (characters);
}
/// <summary> Unset the characters where we should stop. </summary>
public void DontStop ()
{
if (stopstack.Count > 0)
stopstack.Pop ();
else
throw new Exception ("Called DontStop without " +
"calling StopAt before");
}
/// <summary> True when we have to stop here </summary>
private bool StopNow
{
get {
if (stopstack.Count > 0)
foreach (char c in (char []) stopstack.Peek ())
if (c == base.Char)
return true;
return false;
}
}
/// <summary> Start parsing literal </summary>
public new void StartLiteral ()
{
base.StartLiteral ();
// Parsing literal disables stopping
StopAt (new Char [] { });
}
/// <summary> Stop parsing literal </summary>
public new void StopLiteral ()
{
base.StopLiteral ();
DontStop ();
}
/// <summary> Move to the next character in the parse stream. </summary>
public new void Next ()
{
Next (false);
}
/// <summary> Move to the next character in the parse stream. </summary>
public new void Next (bool dropLastNewLine)
{
if ( ! StopNow )
base.Next (dropLastNewLine);
}
/// <summary> The current character </summary>
public new char Char
{
get
{
if (StopNow)
return '\0';
else
return base.Char;
}
}
/// <summary> End of stream/file </summary>
public new bool EOF
{
get { return StopNow || base.EOF; }
}
}
/// <summary>
/// The lookahead buffer, used by the buffer layer in the parser
/// </summary>
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
/// <summary> Character at the current position </summary>
public char Char
{
get
{
if (size > 0)
return buffer [(position + rotation) % buffer.Length];
else
throw new Exception ("Trying to read from an emty buffer");
}
}
/// <summary> The current position </summary>
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' ");
}
}
/// <summary> The last possible postition which could be set </summary>
public int LastPosition
{
get { return size - 1; }
}
/// <summary>
/// The last possible position which could be set if
/// the buffer where full
/// </summary>
public int MaxPosition
{
get { return buffer.Length - 1; }
}
/// <summary> True when the buffer is full </summary>
public bool Full
{
get { return size == buffer.Length; }
}
/// <summary> Current buffer size </summary>
public int Size
{
get { return size; }
}
/// <summary> Append a character to the buffer </summary>
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;
}
/// <summary> Rewind the buffer </summary>
public void Rewind ()
{
position = 0;
}
/// <summary> Reset (clear) the buffer </summary>
public void Clear ()
{
position = -1;
size = 0;
}
/// <summary> Move to the next character </summary>
public void Next ()
{
if (Position < Size)
Position ++;
else throw new Exception ("Cannot move past the buffer");
}
/// <summary>
/// Remove characters from the buffer before the current character
/// </summary>
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;
}
}
}