// Copyright (C) 2008 panacoran <panacoran@users.sourceforge.jp>
// 
// This program is part of Protra.
//
// Protra is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
// 
// $Id: Scanner.cs 263 2010-01-16 13:12:35Z panacoran $

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

namespace Protra.Lib.Lang
{
    /// <summary>
    /// 字句解析を行うクラス。
    /// </summary>
    public class Scanner
    {
        static Regex localVarRegex = new Regex(@"\A[a-z][a-zA-Z_0-9]*", RegexOptions.Compiled);
        static Regex globalVarRegex = new Regex(@"\A\$[a-zA-Z_0-9]+", RegexOptions.Compiled);
        static Regex functionNameRegex = new Regex(@"\A[A-Z][a-zA-Z_0-9]*", RegexOptions.Compiled);
        static Regex floatRegex = new Regex(@"\A(?:[1-9][0-9]*|0)\.[0-9]+", RegexOptions.Compiled);
        static Regex intRegex = new Regex(@"\A(?:[1-9][0-9]*|0)", RegexOptions.Compiled);
        private Buffer buf;
        private Token token;
        private Queue<Token> tokens = new Queue<Token>();

        /// <summary>
        /// 読んだ字句を取得する。
        /// </summary>
        public Token Token
        {
            get { return token; }
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="file">プログラムのファイル名</param>
        public Scanner(string file)
        {
            buf = new Buffer(file);
        }

        /// <summary>
        /// 字句を先読みする。
        /// </summary>
        /// <returns>読んだ字句を返す。</returns>
        public Token Peek()
        {
            Token token = NextToken();
            tokens.Enqueue(token);
            return token;
        }

        /// <summary>
        /// 字句を読む。
        /// </summary>
        /// <returns>EOFに達していたらfalseを返す。</returns>
        public bool Scan()
        {
            if (tokens.Count > 0)
            {
                token = tokens.Dequeue();
                return true;
            }
            token = NextToken();
            if (token.Type == TokenType.EOF)
                return false;
            return true;
        }

        private Token NextToken()
        {
            Match m;
            buf.Next();
            Token token = new Token();
            token.Filename = buf.Filename;
            token.LineNo = buf.LineNo;
            if (buf.Line == null)
            {
                token.Value = null;
                token.Type = TokenType.EOF;
                return token;
            }
            m = localVarRegex.Match(buf.Line, buf.Column, buf.Line.Length - buf.Column);
            if (m.Success)
            {
                token.Value = m.Value;
                switch (token.Value)
                {
                case "if":
                case "else":
                case "elsif":
                case "while":
                case "def":
                case "null":
                    token.Type = TokenType.Reserved;
                    break;
                case "int":
                case "float":
                case "string":
                    token.Type = TokenType.Type;
                    break;
                default:
                    token.Type = TokenType.LocalVariable;
                    break;
                }
                buf.Column += m.Length;
                return token;
            }
            m = globalVarRegex.Match(buf.Line, buf.Column, buf.Line.Length - buf.Column);
            if (m.Success)
            {
                token.Type = TokenType.GlobalVariable;
                token.Value = m.Value;
                buf.Column += m.Length;
                return token;
            }
            m = functionNameRegex.Match(buf.Line, buf.Column, buf.Line.Length - buf.Column);
            if (m.Success)
            {
                token.Type = TokenType.FunctionName;
                token.Value = m.Value;
                buf.Column += m.Length;
                return token;
            }
            m = floatRegex.Match(buf.Line, buf.Column, buf.Line.Length - buf.Column);
            if (m.Success)
            {
                token.Type = TokenType.Float;
                token.FloatValue = double.Parse(m.Value);
                buf.Column += m.Length;
                return token;
            }
            m = intRegex.Match(buf.Line, buf.Column, buf.Line.Length - buf.Column);
            if (m.Success)
            {
                token.Type = TokenType.Int;
                token.IntValue = int.Parse(m.Value);
                buf.Column += m.Length;
                return token;
            }
            switch (buf.Line[buf.Column])
            {
            case ',': case ';': case '(': case ')':
            case '{': case '}': case '[': case ']':
            case '+': case '-': case '*': case '/': case '%':
                token.Type = TokenType.Operator;
                token.Value = buf.Line.Substring(buf.Column, 1);
                buf.Column++;
                return token;
            case '&':
                token.Type = TokenType.Operator;
                token.Value = "&";
                buf.Column++;
                if (buf.Column < buf.Line.Length &&
                    buf.Line[buf.Column] == '&')
                {
                    token.Value = "&&";
                    buf.Column++;
                }
                return token;
            case '|':
                token.Type = TokenType.Operator;
                token.Value = "|";
                buf.Column++;
                if (buf.Column < buf.Line.Length &&
                    buf.Line[buf.Column] == '|')
                {
                    token.Value = "||";
                    buf.Column++;
                }
                return token;
            case '<':
            case '>':
            case '!':
            case '=':
                token.Type = TokenType.Operator;
                token.Value = buf.Line.Substring(buf.Column, 1);
                buf.Column++;
                if (buf.Line[buf.Column] == '=')
                {
                    token.Value += "=";
                    buf.Column++;
                }
                return token;
            case '"':
                token.Type = TokenType.String;
                int start = ++buf.Column;
                while (buf.Column < buf.Line.Length &&
                       buf.Line[buf.Column] != '"')
                    buf.Column++;
                if (buf.Column >= buf.Line.Length)
                    throw new ParseException("string is not closed.", token);
                token.Value = buf.Line.Substring(start, buf.Column++ - start);
                return token;
            }
            token.Type = TokenType.Unknown;
            token.Value = null;
            return token;
        }

        /// <summary>
        /// 読んでいるファイルをクローズする。
        /// </summary>
        public void Close()
        {
            buf.Close();
        }
    }
}
