using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace incparser
{
    enum LexToken
    {
        TOKEN_NONE,
        TOKEN_INCLUDE,
        TOKEN_DEFINE,
        TOKEN_CONST,
        TOKEN_STRUCT,
        TOKEN_FUNCTAG,
        TOKEN_FUNCENUM,
        TOKEN_ENUM,
        TOKEN_NATIVE,
        TOKEN_STOCK,
        TOKEN_PUBLIC,
        TOKEN_FORWARD,
        TOKEN_QUOTCHAR,
        TOKEN_OPERATOR,
        TOKEN_CHARACTER,
        /*TOKEN_STRING,*/
        TOKEN_LABEL,
        TOKEN_SYMBOL,
        TOKEN_DOCBLOCK,
        TOKEN_EOF,
    }

    class ParseException : System.Exception
    {
        public ParseException(string message) : base(message)
        {
        }
    };
         
    class KeywordToken
    {
        public KeywordToken(string k, LexToken t)
        {
            keyword = k;
            token = t;
        }
        public string keyword;
        public LexToken token;
    };

    class Tokenizer
    {
        private static bool s_initialized = false;
        private static int s_position = 0;
        private static KeywordToken [] s_tokens = null;
        public static KeywordToken[] Tokens = null;

        public static void Initialize()
        {
            if (s_initialized)
            {
                return;
            }
            s_tokens = new KeywordToken[12];
            s_initialized = true;
            s_position = 0;

            AddToken("#include", LexToken.TOKEN_INCLUDE);
            AddToken("#define", LexToken.TOKEN_DEFINE);
            AddToken("native", LexToken.TOKEN_NATIVE);
            AddToken("functag", LexToken.TOKEN_FUNCTAG);
            AddToken("funcenum", LexToken.TOKEN_FUNCENUM);
            AddToken("enum", LexToken.TOKEN_ENUM);
            AddToken("stock", LexToken.TOKEN_STOCK);
            AddToken("public", LexToken.TOKEN_PUBLIC);
            AddToken("forward", LexToken.TOKEN_FORWARD);
            AddToken("const", LexToken.TOKEN_CONST);
            AddToken("struct", LexToken.TOKEN_STRUCT);
            AddToken("operator", LexToken.TOKEN_OPERATOR);

            Tokens = s_tokens;
        }

        private static void AddToken(string lex, LexToken tok)
        {
            s_tokens[s_position] = new KeywordToken(lex, tok);
            s_position++;
        }
    };

    class IncParser
    {
        private string FileName;
        private StringBuilder Contents;
        private bool LexPushed;
        private LexToken _LastToken;
        private string _LexString;
        private char _LexChar;
        private uint LineNo;

        public string LexString
        {
            get
            {
                return _LexString;
            }
        }

        public char LexChar
        {
            get
            {
                return _LexChar;
            }
        }

        public uint GetLineNumber()
        {
            return LineNo;
        }

        public IncParser(string file)
        {
            FileName = file;

            /* Clear out lexer stuff */
            LexPushed = false;
            _LastToken = LexToken.TOKEN_NONE;
            _LexString = null;
            _LexChar = '\0';
            LineNo = 1;

            /* Initialize; this can throw an exception */
            Initialize();
        }

         public void Parse(ParseWriter w)
        {
            w.BeginSection(System.IO.Path.GetFileName(FileName));
            LexToken tok = LexToken.TOKEN_NONE;
            while ((tok = lex()) != LexToken.TOKEN_EOF)
            {
                switch (tok)
                {
                    case LexToken.TOKEN_DOCBLOCK:
                        {
                            w.WritePair("doc", LexString);
                            break;
                        }
                    case LexToken.TOKEN_DEFINE:
                        {
                            PARSE_Define(w);
                            break;
                        }
                    case LexToken.TOKEN_ENUM:
                        {
                            PARSE_Enum(w);
                            break;
                        }
                    case LexToken.TOKEN_FORWARD:
                        {
                            PARSE_Function(tok, w);
                            break;
                        }
                    case LexToken.TOKEN_NATIVE:
                        {
                            PARSE_Function(tok, w);
                            break;
                        }
                    case LexToken.TOKEN_STOCK:
                        {
                            PARSE_Function(tok, w);
                            break;
                        }
                    case LexToken.TOKEN_FUNCTAG:
                        {
                            PARSE_FuncTag(w);
                            break;
                        }
                    case LexToken.TOKEN_FUNCENUM:
                        {
                            PARSE_FuncEnum(w);
                            break;
                        }
                    case LexToken.TOKEN_INCLUDE:
                        {
                            PARSE_Include(w);
                            break;
                        }
                    case LexToken.TOKEN_STRUCT:
                        {
                            PARSE_Struct(w);
                            break;
                        }
                    case LexToken.TOKEN_PUBLIC:
                        {
                            PARSE_Public(w);
                            break;
                        }
                    default:
                        {
                            if (_LexString == "static" || _LexString == "new")
                            {
                                PARSE_Public(w);
                                break;
                            }
                            throw new ParseException("Unrecognized token: " + tok + " " + _LexString);
                        }
                }
            }
            w.EndSection();
        }

        private void PARSE_Public(ParseWriter w)
        {
            MatchToken(LexToken.TOKEN_CONST);

            /* Eat up an optional tag */
            MatchToken(LexToken.TOKEN_LABEL);

            /* Eat up a name */
            NeedToken(LexToken.TOKEN_SYMBOL);

            while (MatchChar('['))
            {
                DiscardUntilChar(']');
                NeedChar(']');
            }

            if (MatchChar('='))
            {
                ignore_block();
            }
            else if (MatchChar('(') && MatchChar(')'))
            {
                ignore_block();
                return;
            }

            NeedChar(';');
        }

        private void PARSE_Struct(ParseWriter w)
        {
            /* For now, we completely ignore these (they're not written to the output) */
            NeedToken(LexToken.TOKEN_SYMBOL);
            w.structList.Add(LexString);
            NeedChar('{');

            bool need_closebrace = true;
            bool more_entries = true;
            do
            {
                if (MatchChar('}'))
                {
                    need_closebrace = false;
                    break;
                }
                MatchToken(LexToken.TOKEN_CONST);
                MatchToken(LexToken.TOKEN_LABEL);
                NeedToken(LexToken.TOKEN_SYMBOL);
                while (MatchChar('['))
                {
                    DiscardUntilChar(']');
                    NeedChar(']');
                }
                more_entries = MatchChar(',');
                MatchToken(LexToken.TOKEN_DOCBLOCK);
                if (!more_entries)
                {
                    break;
                }
            } while (true);
            if (need_closebrace)
            {
                NeedChar('}');
            }
            NeedChar(';');
        }

        private void PARSE_Include(ParseWriter w)
        {
            /* read until the end of the line */
            int index = Contents.ToString().IndexOf('\n');
            string value;
            if (index == -1)
            {
                value = Contents.ToString();
                Contents.Remove(0, value.Length);
            }
            else if (index == 0)
            {
                value = "";
            }
            else
            {
                value = Contents.ToString().Substring(0, index);
                Contents.Remove(0, index);
            }

            /* Strip whitespace */
            value = value.Trim('\r', '\t', ' ');

            /* Write */
            w.BeginSection("include");
            w.WritePair("name", value);
            w.EndSection();
        }

        private void PARSE_FuncEnum(ParseWriter w)
        {
            w.BeginSection("funcenum");

            /* Get the functag name */
            NeedToken(LexToken.TOKEN_SYMBOL);
            w.WritePair("name", LexString);
            w.funcenumList.Add(LexString);

            NeedChar('{');

            bool need_closebrace = true;
            do
            {
                /* Shortcut out? */
                if (MatchChar('}'))
                {
                    need_closebrace = false;
                    break;
                }

                /* Start function section */
                w.BeginSection("function");

                if (MatchToken(LexToken.TOKEN_DOCBLOCK))
                {
                    w.WritePair("doc", LexString);
                }
                
                /* Get the return tag */
                if (MatchToken(LexToken.TOKEN_LABEL))
                {
                    w.WritePair("return", LexString);
                }

                /* Get function type */
                if (MatchToken(LexToken.TOKEN_PUBLIC))
                {
                    w.WritePair("type", "public");
                }
                else if (MatchToken(LexToken.TOKEN_STOCK))
                {
                    w.WritePair("type", "stock");
                }
                
                /* Parse the parameters */
                ParseParameters(w);

                /* End the section */
                w.EndSection();

                if (!MatchChar(','))
                {
                    break;
                }
            } while (true);

            if (need_closebrace)
            {
                NeedChar('}');
            }
            NeedChar(';');

            w.EndSection();
        }

        private void PARSE_FuncTag(ParseWriter w)
        {
            w.BeginSection("functag");

            if (MatchToken(LexToken.TOKEN_PUBLIC))
            {
                w.WritePair("type", "public");
            }
            else if (MatchToken(LexToken.TOKEN_STOCK))
            {
                w.WritePair("type", "stock");
            }

            if (MatchToken(LexToken.TOKEN_LABEL))
            {
                w.WritePair("return", LexString);
            }

            /* Get the functag name */
            NeedToken(LexToken.TOKEN_SYMBOL);
            w.WritePair("name", LexString);
            w.functagList.Add(LexString);

            ParseParameters(w);
            NeedChar(';');
            
            w.EndSection();
        }

        private void PARSE_Function(LexToken tok, ParseWriter w)
        {
            string tag="", name;

            /* Get the return value */
            if (MatchToken(LexToken.TOKEN_LABEL))
            {
                tag = LexString;
            }

            if (MatchToken(LexToken.TOKEN_OPERATOR))
            {
                /* Check if we're some sort of invalid function */
                DiscardUntilCharOrComment('(', false);
                ParseParameters(null);
                if (tok == LexToken.TOKEN_STOCK)
                {
                    ignore_block();
                }
                else if (tok == LexToken.TOKEN_NATIVE || tok == LexToken.TOKEN_FORWARD)
                {
                    if (MatchChar('='))
                    {
                        DiscardUntilCharOrComment(';', false);
                    }
                    NeedChar(';');
                }
                return;
            }

            /* Get the name */
            NeedToken(LexToken.TOKEN_SYMBOL);
            name = LexString;

            PARSE_Function(tok, w, tag, name);
        }

        private void PARSE_Function(LexToken tok, ParseWriter w, string tag, string name)
        {
            if (tok == LexToken.TOKEN_FORWARD)
            {
                w.BeginSection("forward");
                w.forwardList.Add(name);
            }
            else if (tok == LexToken.TOKEN_NATIVE)
            {
                w.BeginSection("native");
                w.nativeList.Add(name);
            }
            else if (tok == LexToken.TOKEN_STOCK)
            {
                w.BeginSection("stock");
                w.stockList.Add(name);
            }

            w.WritePair("name", name);

            if (tag.Length > 0)
            {
                w.WritePair("return", tag);
            }

            ParseParameters(w);

            if (tok == LexToken.TOKEN_STOCK)
            {
                ignore_block();
            }
            else
            {
                /* Make sure there's a semicolon */
                if (MatchChar('='))
                {
                    DiscardUntilCharOrComment(';', false);
                }
                NeedChar(';');
            }

            w.EndSection();
        }

        private void ParseParameters(ParseWriter w)
        {
            NeedChar('(');
            string extra;
            do
            {
                if (MatchChar(')'))
                {
                    return;
                }

                /* Start the parameter */
                if (w != null)
                {
                    w.BeginSection("parameter");
                }

                /* Check for a 'const' token */
                if (MatchToken(LexToken.TOKEN_CONST) && w != null)
                {
                    w.WritePair("const", "true");
                }

                /* Check if it's by reference */
                if (MatchChar('&') && w != null)
                {
                    w.WritePair("byref", "true");
                }

                /* Check for a tag */
                if (MatchToken(LexToken.TOKEN_LABEL) && w != null)
                {
                    w.WritePair("tag", LexString);
                }

                /* Get the symbol and write it */
                if (MatchString(0, "...") && w != null)
                {
                    w.WritePair("name", "...");
                }
                else
                {
                    NeedToken(LexToken.TOKEN_SYMBOL);
                    if (w != null)
                    {
                        w.WritePair("name", LexString);
                    }
                }

                /* Check if we have a default value yet */
                bool default_value = false;
                if (MatchChar('='))
                {
                    default_value = true;
                }
                else if (MatchChar('['))
                {
                    extra = "";
                    string temp;
                    do
                    {
                        extra += "[";
                        temp = ConsumeUntilChar(']');
                        if (temp != null)
                        {
                            extra += temp;
                        }
                        extra += "]";
                        NeedChar(']');
                    } while (MatchChar('['));
                    w.WritePair("decoration", extra);
                }

                /* If we have a default value, get it */
                if (default_value || MatchChar('='))
                {
                    if ((extra = ConsumeParamDefValue()) == null)
                    {
                        throw new ParseException("Expected a default value; found none");
                    }
                    if (w != null)
                    {
                        w.WritePair("defval", extra);
                    }
                }

                /* End the parameter */
                if (w != null)
                {
                    w.EndSection();
                }

                if (!MatchChar(','))
                {
                    break;
                }
            } while (true);

            NeedChar(')');
        }

        private void PARSE_Enum(ParseWriter w)
        {
            string name = "";
            if (MatchToken(LexToken.TOKEN_SYMBOL))
            {
                name = LexString;
            }

            w.BeginSection("enum");
            w.WritePair("name", name);
            w.BeginSection("properties");

            w.enumList.Add(name);

            NeedChar('{');
            bool more_entries = true;
            bool need_closebrace = true;

            if (MatchToken(LexToken.TOKEN_DOCBLOCK))
            {
              w.WritePair("doc", LexString);
            }

            do
            {
                if (MatchChar('}'))
                {
                    need_closebrace = false;
                    break;
                }
                NeedToken(LexToken.TOKEN_SYMBOL);
                name = LexString;
 
                if (MatchChar('='))
                {
                    DiscardUntilCharOrComment(",\n}", true);
                }
                more_entries = MatchChar(',');
                w.WritePair("name", name);
                w.enumTypeList.Add(name);
                if (MatchToken(LexToken.TOKEN_DOCBLOCK))
                {
                    w.WritePair("doc", LexString);
                }
            } while (more_entries);
            if (need_closebrace)
            {
                NeedChar('}');
            }
            NeedChar(';');
            w.EndSection();
            w.EndSection();
        }

        private void PARSE_Define(ParseWriter w)
        {
            /* first, get the symbol name */
            NeedToken(LexToken.TOKEN_SYMBOL);
            string name = LexString;
            string value;

            /* read until we hit the end of a line OR a comment */
            int endpoint = -1;
            for (int i = 0; i < Contents.Length; i++)
            {
                if (Contents[i] == '\n')
                {
                    endpoint = i;
                    break;
                }
                else if (CheckString(i, "/**"))
                {
                    endpoint = i;
                    break;
                }
            }

            if (endpoint < 1)
            {
                value = "";
            }
            else
            {
                value = Contents.ToString().Substring(0, endpoint);
                Contents.Remove(0, endpoint);
            }

            /* Write */
            w.defineList.Add(name);

            w.BeginSection("define");
            w.WritePair("name", name);
            w.WritePair("linetext", value);
            w.EndSection();
        }

        /* Reads up to a character and discards all text before it.
         */
        private void DiscardUntilChar(char c)
        {
            int eol = Contents.ToString().IndexOf(c);

            if (eol < 1)
            {
                return;
            }

            Contents.Remove(0, eol);
        }

        /* Same as DiscardUntilChar, except the discarded
         * text is returned first.
         */
        private string ConsumeUntilChar(char c)
        {
            int eol = Contents.ToString().IndexOf(c);

            if (eol < 1)
            {
                return null;
            }

            string rest = Contents.ToString().Substring(0, eol);
            Contents.Remove(0, eol);
            return rest;
        }

        /**
         * Returns a parameter's default value.
         */
        private string ConsumeParamDefValue()
        {
            int eol = FindEndOfExprLine(0);
            if (eol < 1)
            {
                return null;
            }

            string rest = Contents.ToString().Substring(0, eol);
            Contents.Remove(0, eol);
            return rest;
        }

        /**
         * Discards all data up to a certain character, ignoring
         * the contents, unless specified to stop at a doc block comment.
         */
        private void DiscardUntilCharOrComment(char c, bool stop_at_comment)
        {
            for (int i = 0; i < Contents.Length; i++)
            {
                if (Contents[i] == '\n')
                {
                    LineNo++;
                }
                else if (Contents[i] == c)
                {
                    if (i > 0)
                    {
                        Contents.Remove(0, i);
                    }
                    return;
                }
                else if (stop_at_comment && CheckString(i, "/**"))
                {
                    if (i > 0)
                    {
                        Contents.Remove(0, i);
                    }
                    return;
                }
            }
        }

       /**
        * Discards all data up to a certain character, ignoring
        * the contents, unless specified to stop at a doc block comment.
        */
        private void DiscardUntilCharOrComment(string s, bool stop_at_comment)
        {
          for (int i = 0; i < Contents.Length; i++)
          {
            if (Contents[i] == '\n')
            {
              LineNo++;
            }
            else if (s.Contains(Contents[i].ToString()))
            {
              if (i > 0)
              {
                Contents.Remove(0, i);
              }
              return;
            }
            else if (stop_at_comment && CheckString(i, "/**"))
            {
              if (i > 0)
              {
                Contents.Remove(0, i);
              }
              return;
            }
          }
        }

        /**
         * Finds the end of an expression
         */
        private int FindEndOfExprLine(int start)
        {
            for (int i = start; i < Contents.Length; i++)
            {
                if (Contents[i] == '{')
                {
                    if ((i = FindEndOfConsumptionLine('}', start+1)) == -1)
                    {
                        throw new ParseException("Expected token: } (found none)");
                    }
                }
                else if (Contents[i] == ')' || Contents[i] == ',')
                {
                    return i;
                }
            }

            return -1;
        }

        private int FindEndOfConsumptionLine(char c, int start)
        {
            for (int i = start; i < Contents.Length; i++)
            {
                if (Contents[i] == '{')
                {
                    if ((i = FindEndOfConsumptionLine('}', i + 1)) == -1)
                    {
                        throw new ParseException("Expected token: } (found none)");
                    }
                }
                else if (Contents[i] == c)
                {
                    return i;
                }
            }

            return -1;
        }

        private void Initialize()
        {
            /* Open file, read it, and close it */
            StreamReader sr = File.OpenText(FileName);
            Contents = new StringBuilder(sr.ReadToEnd());
            sr.Close();

            /* Strip comments (may throw an exception) */
            preprocess();

            /* Lastly, initialize the tokenizer */
            Tokenizer.Initialize();
        }

        /* Consumes a token iff it matches */
        private bool MatchToken(LexToken token)
        {
            LexToken tok = lex();
            if (tok != token)
            {
                lexpush();
                return false;
            }
            return true;
        }

        /* Consumes a token and aborts the parser if there is no match */
        private void NeedToken(LexToken token)
        {
            if (!MatchToken(token))
            {
                throw new ParseException("Expected token: " + token);
            }
        }

        /* Consumes a character iff it matches */
        private bool MatchChar(char c)
        {
            if (lex() != LexToken.TOKEN_CHARACTER)
            {
                lexpush();
                return false;
            }
            if (_LexChar != c)
            {
                lexpush();
                return false;
            }
            return true;
        }

        /* Consumes a character or aborts the parser if there is no match */
        private void NeedChar(char c)
        {
            if (!MatchChar(c))
            {
                throw new ParseException("Expected character: " + c);
            }
        }
        
        /* Saves the lexer state so we don't reparse unnecessarily */
        private void lexpush()
        {
            if (LexPushed)
            {
                throw new ParseException("Lexer cannot be pushed twice");
            }
            LexPushed = true;
        }

        /* Attempts to match a string against the next stream tokens.
         * On success, the token stream is advanced.
         */
        private bool MatchString(int startPos, string str)
        {
            if (str.Length > Contents.Length - startPos)
            {
                return false;
            }
            for (int i = 0; i < str.Length; i++)
            {
                if (str[i] != Contents[startPos + i])
                {
                    return false;
                }
            }
            Contents.Remove(startPos, str.Length);
            return true;
        }

        /* Attempts to match a string against the next stream tokens */
        private bool CheckString(int startPos, string str)
        {
            if (str.Length > Contents.Length - startPos)
            {
                return false;
            }
            for (int i = 0; i < str.Length; i++)
            {
                if (str[i] != Contents[startPos + i])
                {
                    return false;
                }
            }
            return true;
        }

        /* Ignores a block starting and ending with { and } respectively */
        private void ignore_block()
        {
            /* Hrm, this is tricky.  First, expect an open brace. */
            NeedChar('{');
            /* Consume the entire function by keeping track of braces */
            int numBraces = 1;
            bool in_string = false;
            bool found_end = false;
            for (int i = 0; i < Contents.Length; i++)
            {
                if (Contents[i] == '\n')
                {
                    LineNo++;
                }
                if (!in_string)
                {
                    if (Contents[i] == '"')
                    {
                        in_string = true;
                    }
                    else if (Contents[i] == '{')
                    {
                        numBraces++;
                    }
                    else if (Contents[i] == '}')
                    {
                        if (numBraces == 0)
                        {
                            throw new ParseException("Unable to find a matching open brace");
                        }
                        if (--numBraces == 0)
                        {
                            Contents.Remove(0, i + 1);
                            found_end = true;
                            break;
                        }
                    }
                }
                else
                {
                    /* It is possible to be in a string and at the first character */
                    if (Contents[i] == '"' && Contents[i - 1] != '\\')
                    {
                        in_string = false;
                    }
                }
            }
            if (!found_end)
            {
                throw new ParseException("Unable to find a matching close brace");
            }
        }

        /* Strips all non-doc C and C++ style comments. as well as unused pragmas */
        private void preprocess()
        {
            if (Contents.Length == 1)
            {
                return;
            }

            bool in_comment = false;
            bool is_ml = false;
            bool is_doc = false;
            bool is_newline = true;

            for (int i=0; i<Contents.Length; i++)
            {
                if (Contents[i] == '\n')
                {
                    is_newline = true;
                }
                if (!in_comment)
                {
                    if (Contents[i] == '/')
                    {
                        if (Contents[i+1] == '/')
                        {
                            in_comment = true;
                            is_ml = false;
                            is_doc = false;
                            is_newline = false;
                        } else if (Contents[i+1] == '*') {
                            /* Detect whether this is a doc comment or not */
                            if (Contents.Length - i >= 3
                                && Contents[i+2] == '*')
                            {
                                is_doc = true;
                            } else {
                                is_doc = false;
                            }
                            in_comment = true;
                            is_ml = true;
                        }
                        if (in_comment && !is_doc)
                        {
                            Contents[i] = ' ';
                            Contents[i+1] = ' ';        /* Just for safety */
                        }
                    }
                    else if (is_newline)
                    {
                        if (Contents[i] == '#')
                        {
                            if (!CheckString(i, "#define")
                                && !CheckString(i, "#include"))
                            {
                                /* Ignore! */
                                Contents[i] = ' ';
                                in_comment = true;
                                is_doc = false;
                                is_ml = false;
                            }
                            is_newline = false;
                        }
                        else if (!Char.IsWhiteSpace(Contents[i]))
                        {
                            is_newline = false;
                        }
                    }
                } else {
                    if (is_ml)
                    {
                        if (Contents[i] == '*' && Contents[i+1] == '/')
                        {
                            if (!is_doc)
                            {
                                Contents[i] = ' ';
                                Contents[i+1] = ' ';
                            }
                            in_comment = false;
                            i++;    /* Don't look at the next character */
                        }
                        else if (!is_doc && Contents[i] != '\n')
                        {
                            /* Erase comment */
                            Contents[i] = ' ';
                        }
                    } else {
                        if (is_newline)
                        {
                            in_comment = false;
                        }
                        else
                        {
                            /* Erase comment */
                            Contents[i] = ' ';
                        }
                    }
                }
            }

            if (in_comment)
            {
                throw new ParseException("Multi-line comment has no terminator");
            }
        }

        /* Strips whitespace */
        private void trim_whitespace_pre()
        {
            int spaces = 0;
            while (spaces < Contents.Length
                   && Contents[spaces] != '\0'
                   && Char.IsWhiteSpace(Contents[spaces]))
            {
                if (Contents[spaces] == '\n')
                {
                    LineNo++;
                }
                spaces++;
            }
            if (spaces > 0)
            {
                Contents.Remove(0, spaces);
            }
        }

        private void preprocess_line()
        {
            trim_whitespace_pre();
        }

        private bool IsSymbolChar(char c, bool first)
        {
            bool valid = (first ? Char.IsLetter(c) : Char.IsLetterOrDigit(c));
            return (valid || (c == '_'));
        }

        private bool IsEscapedChar(char c)
        {
            return (c == 'n'
                    || c == 'r'
                    || c == 't'
                    || c == 'v'
                    || c == '\\'
                    || c == '"');
        }

        private bool IsNotKeywordChar(char c)
        {
            return (Char.IsWhiteSpace(c)
                    || (c == '-' || c == '+')
                    || (c == '/' || c == '*')
                    || (c == '(' || c == ')')
                    || (c == '=' || c == '%')
                    || (c == '>' || c == '<')
                    || (c == '!' || c == '~')
                    );
        }

        /* Consumes one lexical token and gathers information about it */
        private LexToken lex()
        {
            if (LexPushed)
            {
                LexPushed = false;
                return _LastToken;
            }

            /* Number of chars we will be deleting from the input */
            int stripchars = 0;

            /* Clear our state */
            _LastToken = LexToken.TOKEN_NONE;

            /* Remove stuff we don't want from the line */
            preprocess_line();

            if (Contents.Length < 1)
            {
                _LastToken = LexToken.TOKEN_EOF;
                return _LastToken;
            }
            
            /* Get the token list */
            KeywordToken[] tokens = Tokenizer.Tokens;
            for (int i = 0; i < tokens.Length; i++)
            {
                if (!CheckString(0, tokens[i].keyword))
                {
                    continue;
                }
                int len = tokens[i].keyword.Length;
                /* Now check to see what the next token is */
                if (Contents.Length == len
                    || IsNotKeywordChar(Contents[len]))
                {
                    /* We have a token match! */
                    _LastToken = tokens[i].token;
                    _LexString = null;
                    _LexChar = '\0';
                    stripchars = len;
                    break;
                }
            }

            if (_LastToken == LexToken.TOKEN_NONE)
            {
                /* See if we can try to read a symbol */
                if (IsSymbolChar(Contents[0], true))
                {
                    int characters = 1;
                    while (Contents[characters] != '\0'
                           && IsSymbolChar(Contents[characters], false))
                    {
                        characters++;
                    }
                    stripchars = characters;
                    /* We're done! See what's next.. */
                    if (Contents[characters] == ':')
                    {
                        _LastToken = LexToken.TOKEN_LABEL;
                        stripchars++;
                    }
                    else
                    {
                        _LastToken = LexToken.TOKEN_SYMBOL;
                    }
                    _LexString = Contents.ToString().Substring(0, characters);
                    _LexChar = _LexString[0];
                }/*
                else if (Contents[0] == '"')
                {
                    bool reached_end = false;
                    StringBuilder builder = new StringBuilder();
                    for (int i = 1; i < Contents.Length; i++)
                    {
                        if (i < Contents.Length - 1
                            && Contents[i] == '\\'
                            && IsEscapedChar(Contents[i+1]))
                        {
                            i++;
                        }
                        else if (Contents[i] == '"')
                        {
                            reached_end = true;
                            break;
                        }
                        builder.Append(Contents[i]);
                    }
                    if (!reached_end)
                    {
                        throw new ParseException("Expected end of string; none found");
                    }
                    _LexString = builder.ToString();
                    _LexChar = _LexString.Length > 0 ? _LexString[0] : '\0';
                }*/
                else if (Contents[0] == '\'')
                {
                    char c = '\0';
                    if (Contents.Length < 3)
                    {
                        throw new ParseException("Expected end of character; none found");
                    }
                    else if (Contents[1] == '\\')
                    {
                        if (Contents.Length < 4 || !IsEscapedChar(Contents[2]))
                        {
                            throw new ParseException("Expected end of character; none found");
                        }
                        if (Contents[2] == 'r')
                        {
                            c = '\r';
                        }
                        else if (Contents[2] == 'n')
                        {
                            c = '\n';
                        }
                        else if (Contents[2] == 't')
                        {
                            c = '\t';
                        }
                        else if (Contents[2] == 'v')
                        {
                            c = '\v';
                        }
                        else if (Contents[2] == '\\')
                        {
                            c = '\\';
                        }
                        else if (Contents[2] == '"')
                        {
                            c = '"';
                        }
                        stripchars = 4;
                    }
                    else
                    {
                        c = Contents[1];
                        stripchars = 3;
                    }
                    _LexString = c.ToString();
                    _LexChar = c;
                    _LastToken = LexToken.TOKEN_QUOTCHAR;
                }
                else if (CheckString(0, "/**"))
                {
                    int endpoint = 0;
                    for (int i=3; i<Contents.Length-1; i++)
                    {
                        if (Contents[i] == '\n')
                        {
                            LineNo++;
                        }
                        else if (Contents[i] == '*' && Contents[i+1] == '/')
                        {
                            endpoint = i+1;
                            break;
                        }
                    }
                    if (endpoint == 0)
                    {
                        throw new ParseException("Expected end of multi-line comment; found end of file");
                    }
                    /* We have to factor one more character in because we're zero based */
                    endpoint++;
                    _LastToken = LexToken.TOKEN_DOCBLOCK;
                    _LexString = Contents.ToString().Substring(0, endpoint);
                    _LexChar = _LexString[0];
                    stripchars = endpoint;
                }
                else
                {
                    _LastToken = LexToken.TOKEN_CHARACTER;
                    _LexString = Contents[0].ToString();
                    _LexChar = Contents[0];
                    stripchars = 1;
                }
            }

            /* Strip N chars */
            if (stripchars > 0)
            {
                Contents.Remove(0, stripchars);
            }

            return _LastToken;
        }
    }
}