/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
  <file> ShellLex.cs </file>
  <brief>
    Tokenize and dump the shell file.
  </brief>
  <author>
    Stanley Hong <link2next@gmail.com>, Dec. 2013.
  </author>
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using uls.collection;
//
// To generate the file 'ShellLexBasis.cs' from 'shell.ulc', use Ulc2Class.exe
//    Ulc2Class.exe -lcs -o ShellLexBasis.cs -n uls.nemesis.ShellLexBasis shell.ulc
//

namespace uls
{
    namespace tests
    {
        namespace ShellToks
        {
            public class ShellLex : ShellLexBasis
            {
                public const String SHELL_SPECIAL_CHARS = "$+-_./=\\!*?~";
                StringBuilder tokbuf;

                private String tok_str;
                int tok_id;
                bool tok_ungot;

                private bool is_space(uint ch)
                {
                    bool rval;
                    rval = (ch == ' ' || ch == '\t' || ch == '\n') ? true : false;
                    return rval;
                }

                private bool is_digit(uint ch)
                {
                    bool rval;
                    rval = (ch >= '0' && ch <= '9') ? true : false;
                    return rval;
                }

                private bool is_alpha(uint ch)
                {
                    bool rval;
 
                    if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
                        rval = true;
                    } else {
                        rval = false;
                    }

                    return rval;
                }

                private bool is_alnum(uint ch)
                {
                    bool rval;
                    rval = (is_digit(ch) || is_alpha(ch)) ? true : false;
                    return rval;
                }

                public ShellLex(String config_name)
                    : base(config_name)
                {
                    tokbuf = new StringBuilder();
                    tok_id = NONE;
                    tok_str = "";
                    tok_ungot = false;
                }

                private int expect_number()
                {
                    int tok;

                    tok = base.getTok();
                    base.expect(NUM);

                    tokbuf.Append("0x");
                    tokbuf.Append(base.TokStr);

                    return tok;
                }

                private void expect_redir()
                {
                    uint ch;

                    while ((ch=base.getCh()) != NEXTCH_NONE) {
                        if (ch == ' ' || ch == '\t') {
                            base.ungetCh(ch);
                            break;
                        }

                        tokbuf.Append((char)ch);
                    }

                    tok_str = tokbuf.ToString();
                }

                private bool isSpecialChar(String str, uint ch)
                {
                	bool rval;

                    if (str.IndexOf((char)ch) < 0 && SHELL_SPECIAL_CHARS.IndexOf((char)ch) < 0)
                    	rval = false;
                    else
                    	rval = true;
  
                    return rval;
                }

                public override int getTok()
                {
 	                int tok;
	                uint ch;

                    if (tok_ungot == true)
                    {
                        tok_ungot = false;
                        return tok_id;
                    }

                    if ((ch = base.skipBlanks()) == NEXTCH_NONE)
                    {
		                tok_id = tok = base.getTok();
		                tok_str = base.TokStr;
                        return tok;
	                }

	                tokbuf.Length = 0;

	                if (ch == '-') { // Is '-' the minus sign?
                        ch = base.getCh();
                        tokbuf.Append('-');

		                ch = base.peekCh();
		                if (is_digit(ch)) {
			              tok = expect_number();
			                // The number-token in shell-script can contain minus sign.
		                } else {
			                tok = WORD;
		                }

	               } else if (is_digit(ch)) {
		                tok = expect_number();

	               } else if (is_alpha(ch) || ch == '_') {
		                // keyword?
		                tok = base.getTok();
                        tokbuf.Append(base.TokStr);

	               } else if (ch == '&') {
                        ch = base.getCh();
                        tokbuf.Append('&');
		                tok = '&';

	               } else {
		                tok = NONE;
	               }

                    if ((ch = base.peekCh()) == NEXTCH_NONE)
                    {
                        if (base.isQuoteTok(base.getTok()))
                        {
                            if (tok == NONE) tok = base.TokNum;
                            tok_str = tokbuf.ToString();
                        }
                        else
                        {
                            tok = base.getTok();
                            tokbuf.Append(base.TokStr);
                            tok_str = tokbuf.ToString();
                        }
                        tok_id = tok;
                        return tok_id;
                    }

	               if (ch == '<' || ch == '>') {
                        ch = base.getCh();
		                expect_redir();
		                tok_id = tok = REDIRECT;
		                return tok;
	               }

	               if (is_alnum(ch) || isSpecialChar("'\"`", ch) == true) {
		              // 'Shell-Script word' is to be processed, ...
		               do {
			              if (is_digit(ch)) {
				              expect_number();

			              } else if (is_alpha(ch)) {
				              base.getTok();
                              tokbuf.Append(base.TokStr);

			              } else if (ch == '"' || ch == '\'' || ch == '`') {
	 			              base.getTok();
                              tokbuf.Append(base.TokStr);
                              tokbuf.Append((char)ch);

	 		              } else {
                              if (isSpecialChar("@#(){}", ch) == false) {
                                  break;
                              }
 
                              ch = base.getCh();
                              tokbuf.Append((char)ch);
			              }

	 	                } while ((ch=base.peekCh()) != NEXTCH_NONE);

		                tok = WORD;
	                }

	                if (tok == NONE) {
		                tok = base.getTok();
		                tokbuf.Append(base.TokStr);
	                }

	                tok_str = tokbuf.ToString();
	                tok_id = tok;

	                return tok;
                }

                public override void ungetTok()
                {
                    tok_ungot = true;
                }

                public override int TokNum
                {
                    get
                    {
                        return tok_id;
                    }
                }

                public override String TokStr
                {
                    get
                    {
                        return tok_str;
                    }
                }

                public bool source(String fpath)
                {
                    pushFile(fpath);
                    tok_str = "";
                    tok_id = NONE;
                    return true;
                }
            }
        }
    }
}
