/*

CLASS {Parser|_of_source_for_primary_compiler_only}, UNTESTED : 

IMPORT: {Compiler}, {Type}, {Variable}, {Function},
		{Expression}, {Localize_str}, {E_op} ;
		
IMPORT: {Text_parser} ;

BASE CLASS {Text_parser}


*/

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

namespace AL4Compiler
{
    public class Parser
    {
        public Parser()
        {
        }

        /*
        
        {Type}      Class_|where_from_
        STR Syntax_name|_dummy_for_primary
    	METHOD Convert_from_pascal|_dummy_in_case_of_primary: .
    	METHOD Convert_SEAL|_dummy_in_case_of_primary: .
    	BOOL Case_insensitive|_dummy
    	BOOL Allow_omit_array_brackets
    	BOOL Allow_assign_to_fields
    	BOOL Ignore_function_result
    	BOOL Not_restrict_nesting
    	BOOL Remove_ending
    	BOOL Clone_by_default
        
        METHOD Compiler|_object ==> {Compiler} :
			RESULT = Compiler_.{Compiler} .
			
		METHOD Style_warn|ing(STR M|essage_text): Warn(M) .
			
		METHOD Index_warn|ing(STR M|essage_text): Warn(M) .
			
        */
        public Compiler Compiler_ = null;
        public Type Class_ = null;
        public List<string> Source = new List<string>();
        public int Line;
        public int Pos;

        public string Next_char()
        {
            string Result = "";
            if (Pos <= Last() && Source.Count > Line)
                Result = Source[Line][Pos] + "";
            else Result = (Char)0 + "";
            return Result;
        }

        public string Rest_from_pos
        {
            get
            {
                return Source[Line].Substring(Pos, Source[Line].Length - Pos);
            }
        }
        
        public int Last()
        {
            if (Line >= Source.Count) return 0;
            return Source[Line].Length - 1;
        }

        public void Skip_char()
        {
            Pos++;
        }
        
        public void Skip_sp()
        {
            for (int i = Pos; i <= Last() + 1; i++)
            {
                Pos = i;
                if (" \t\r\n".Contains(Next_char())) { }
                else break;
            }
        }
        
        public string Skip_to(string Sym)
        {
            int from = Pos;
            for (int i = Pos; i <= Source[Line].Length - Sym.Length; i++)
            {
                Pos = i;
                if (Source[Line].Substring(Pos, Sym.Length) == Sym)
                    return Source[Line].Substring(from, Pos - from);
            }
            return Source[Line].Substring(from, Source[Line].Length - from);
        }

        public bool Ending_incl = false;

        public string String_const(bool Single)
        {
            if (Symbol("@")) Ending_incl = true;
            string Result = "";
            bool ok = false;
            try
            {
                for (int i = Pos; i <= Last(); i++)
                {
                    for (int y = Pos; y <= Last(); y++)
                    {
                        if (End_of_line()) break;
                        if (Ending_incl && "\r\n".Contains(Next_char()))
                            Result += Next_char();
                        if ((Next_char() + (char)0)[0] <= ' ') Pos++;
                        else break;
                    }
                    switch (End_of_line())
                    {
                        case true: goto label_end_i;
                    }
                    switch (Next_char()[0])
                    {
                        case '\"': Result = Result + string_from_Pos();
                            ok = true; break;
                        case '#':
                            ok = true;
                            Pos++;
                            switch ("0123456789".Contains(Next_char()))
                            {
                                case true: Result = Result + (Char)Number(); break;
                                default: Result = Result + Encoded_char(); break;
                            }
                            break;
                    }
                    switch (Single)
                    {
                        case true: return Result;
                    }
                }
            }
            finally
            {
                if (!ok) Err("Waiting for string constant but: " + Next20());
            }
        label_end_i: return Result;
        }

        private string string_from_Pos()
        {
            string Result = "";
            int from = Pos + 1;
            for (int j = from; j <= Source[Line].Length; j++)
            {
                Pos = j;
                switch (Next_char()[0])
                {
                    case '\"': Result = Source[Line].Substring(from, Pos++ - from);
                        return Result;
                }
            }
            Err("String constant is not finished");
            return Result;
        }

        public string Encoded_char()
        {
            string Result = "";
            string id = Ident();
            bool sw_ok = false;
            if (id != "")
                switch (id[0])
                {
                    case 'N': if (id == "NL")
                        {
                            Result = "" + (Char)13 + (Char)10;
                            sw_ok = true;
                        }
                        break;
                    case 'L': if (id == "LF")
                        {
                            Result = "" + (Char)10;
                            sw_ok = true;
                        }
                        break;
                    case 'C': if (id == "CR")
                        {
                            Result = "" + (Char)13;
                            sw_ok = true;
                        } break;
                    case 'T': if (id == "TAB")
                        {
                            Result = "" + (Char)9;
                            sw_ok = true;
                        }
                        break;
                    case 'B': if (id == "BK")
                        {
                            Result = "" + (Char)8;
                            sw_ok = true;
                        }
                        else if (id == "BELL")
                        {
                            Result = "" + (Char)7;
                            sw_ok = true;
                        }
                        break;
                    case 'S': if (id == "SP")
                        {
                            Result = " ";
                            sw_ok = true;
                        }
                        break;
                    case 'Q': if (id == "QU")
                        {
                            Result = "\"";
                            sw_ok = true;
                        }
                        break;
                }
            if (!sw_ok)
                Err("Unknown char code: " + id);
            return Result;
        }

        static int CNT_Ident;
        public bool no_errors;

        public string Ident()
        {
            if (++CNT_Ident == 13147)
            { }

            string Result = "";
            Skip_sp();
            int from = Pos;
            switch (Is_letter() || Next_char() == "_")
            {
                case false: 
                	if (!no_errors)
						Err("Not an identifier: " + Next20()); 
                	break;
            }
            for (int i = Pos; i <= Last()+1; i++)
            {
                Pos = i;
                switch (Is_letter() || Pos < Last()+1 && Source.Count > Line && "01234567890_".Contains(Source[Line][Pos]))
                {
                    case false: goto end_for_i;
                }
            }
            end_for_i: Result = Source.Count > Line && from < Source[Line].Length ? Source[Line].Substring(from, Pos - from) : "";
            return Result;
        }

        public void Scan_names(List<string> Names)
        {
        	no_errors = true;
            Names.Add(Ident());
            no_errors = false;
            if (Symbol("|"))
            {
                string s = Names[Names.Count - 1];
                Names.Add(s + Ident());
            }
        }

		public void Check_names(List<string> Names)
        {
            if (Names[Names.Count - 1].Length < 8) {
            	if (next_is_indexing()) return;
                Err("Full name length must exceed 7 characters: " +
                    Names[Names.Count - 1]);
            }
            if (Names.Count > 1)
            {
                string s1 = Names[Names.Count - 2];
                string s2 = Names[Names.Count - 1];
                if (s1 != "" && s2 != "" && (s1[s1.Length - 1] == '_') != (s2[s2.Length - 1] == '_'))
                    Err("Both short and long names must be ended with or without trailing '_'");
            }
        }
        
        public bool next_is_indexing() 
        {
        	bool result = Next_symbol("INDEXING");
        	if (result) return true;
        	int save_pos = Pos;
        		result = Symbol("[")
        		if (result) {
        			if (Symbol("*")) {}
        			result = Symbol("]")
        		}
        	Pos = save_pos;
        	return result;
        }        

        public void Check_reserved(List<string> Names)
        {
            foreach (string n in Names)
            {
                bool yes = false;
                switch ((n + (char)0)[0])
                {
                    case 'T':
                        if (n == "THIS" || n == "TRUE" || n == "TO") yes = true; break;
                    case 'R':
                        if (n == "RESULT" || n == "REAL") yes = true; break;
                    case 'F':
                        if (n == "FOR" || n == "FOREVER" || n == "FUN" || n == "FUNCTION" || n == "FALSE")
                            yes = true;
                        break;
                    case 'S':
                        if (n == "CASE" || n == "STR" || n == "STRING" || n == "STOP") yes = true;
                        break;
                    case 'P':
                        if (n == "PUSH") yes = true;
                        break;
                    case 'C':
                        if (n == "CONTINUE" || n == "CLASS") yes = true;
                        break;
                    case 'B':
                        if (n == "BREAK" || n == "BOOL" || n == "BOOLEAN") yes = true;
                        break;
                    case 'I':
                        if (n == "INT" || n == "INTEGER" || n == "INDEX") yes = true;
                        break;
                    case 'D':
                        if (n == "DOWNTO" || n == "DATA") yes = true;
                        break;
                }
                if (yes)
                    Err("Re-using reserved keyword: " + n);
            }
        }

        public void Check_local_names(List<string> Names)
        {
            foreach (string n in Names)
            {
                if (((n + (char)0)[0] + "").ToUpper() == (n + (char)0)[0] + "")
                    Warn("It is not recommended to use " +
                        "names starting from uppercase letter for local variables");
                if (n.Length > 0 && n[n.Length - 1] == '_')
                    Err("Declaring local variable as a weak reference to an object " +
                        "using trailing '_' is not allowed");
            }
        }

        public string Scan_type()
        {
            const string obr = "{";
            const string cbr = "}";
            string Result = "";
            if (Symbol(obr))
            {
                Result = obr + Ident() + cbr;
                Wait(cbr);
                int before_try = Pos;
                if (Symbol(".") && Symbol(obr))
                {
                    Result += "." + obr + Ident() + cbr;
                    Wait(cbr);
                }
                else
                    Pos = before_try;
            }
            else Result = Ident();
            return Result;
        } 

        const char obr = '{';
        const char cbr = '}';

        public string Class_name()
        {
            Wait("" + obr);
            string Result = "" + obr + Ident() + cbr;
            Wait("" + cbr);
            return Result;
        }

        public int Number()
        {
            try
            {
                if (!"0123456789".Contains(Next_char()))
                    Err("Not an integer number: " + Next20());
                string nx = Line < Source.Count && Source[Line].Length > Pos + 1 ?
                      Source[Line].Substring(Pos, 2).ToLower() : "";
                if (nx == "0x")
                {
                    Pos += 2;
                    return Radix_number(16);
                }
                if (nx == "0b")
                {
                    Pos += 2;
                    return Radix_number(2);
                }
                if (nx == "0o")
                {
                    Pos += 2;
                    return Radix_number(8);
                }
                return Radix_number(10);
            }
            catch
            { return 0; }
        }
        
        public int Radix_number(int Base)
        {
            int Result = 0;
            Skip_sp();
            bool bad_digit = false;
            bool told = false;
            bool fin = false;
            for (int i = Pos; i <= Last() + 1 && !fin; i++)
            {
                Pos = i;
                char c = (Next_char() + (char)0)[0];
                switch (c)
                {
                    case '_': break;
                    case '0': case '1': case '2': case '3': case '4': case '5': case '6':
                    case '7': case '8': case '9':
                        Result = Result * Base + (int)(Next_char() + " ")[0] - (int)'0';
                        bad_digit = (int)c - (int)'0' >= Base;
                        break;
                    case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
                        Result = Result * Base + (int)(Next_char() + " ")[0] + 10 - (int)'A';
                        char nx = (Next_char() + (char)0)[0];
                        bad_digit = (int)nx - (int)'A' + 10 >= Base;
                        break;
                    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
                        Result = Result * Base + (int)(Next_char() + " ")[0] + 10 - (int)'A';
                        nx = (Next_char() + (char)0)[0];
                        bad_digit = (int)nx - (int)'a' + 10 >= Base;
                        break;
                    default: fin = true; break;
                }
                if (bad_digit && !told)
                {
                    Err("Bad digit for base " + Base.ToString() + ": " + Next20());
                    told = true;
                }
            }
            return Result;
        }

        public double Real_number()
        {
            Skip_sp();
            bool neg = false;
            if (Next_char() == "-")
            {
                neg = true;
                Skip_char();

                Skip_sp();
            }
            double Result = Number();
            if (Next_char() == ".")
            {
                Skip_char();
                if ("0123456789".Contains(Next_char())) { }
                else Err("Digits must follow '.' in real number");
                double divider = 1;
                for (int xdummy = Pos; xdummy <= Last(); xdummy++)
                {
                    if (Next_char() == "_") { Skip_char(); continue; }
                    if ("0123456789".Contains(Next_char()))
                    {
                        divider /= 10;
                        Result += ((int)Next_char()[0] - (int)'0') * divider;
                        Skip_char();
                    }
                    else break;
                }
            }
            if ("eE".Contains(Next_char()))
            {
                Skip_char();
                int e = Integer();
                Result *= Math.Pow(10, e);
            }
            if (neg)
                Result = -Result;
            return Result;
        }

        public int Integer()
        {
            Skip_sp();
            bool neg = false;
            if (Symbol("-")) neg = true;
            int Result = Number();
            if (neg) Result = -Result;
            return Result;
        }
        
        public bool End_of_line()
        {
            return Line >= Source.Count || Pos >= Source[Line].Length;
        }

        public bool End_of_source()
        {
            return Line >= Source.Count || Line == Source.Count-1 && End_of_line();
        }

        public void Fin()
        {
            Skip_sp();
            if (!End_of_line()) Err("Ambiguous characters: " + Next20());
            for (int i = Line + 1; i <= Source.Count; i++)
            {
                Line = i;
                Pos = 0;
                Skip_sp();
                if (!End_of_line()) return;
            }
        }

        public bool Is_letter()
        {
            string U = Next_char().ToUpper();
            string L = Next_char().ToLower();
            return U != L;
        }

        public string Next20()
        {
            int L = 20;
            if (Last() < Pos + L)
                L = Last() - Pos + 1;
            if (L <= 0) return "";
            return Source.Count > Line ? Source[Line].Substring(Pos, L) : "";
        }

        public bool Symbol(string S)
        {
            Skip_sp();
            if (End_of_line()) return false;
            int L = S.Length;
            if (L + Pos > Source[Line].Length)
                return false;
            if (Source[Line].Substring(Pos, L) == S)
            {
                bool was_letter = Is_letter();
                Pos += S.Length;
                if (was_letter && Is_letter())
                {
                    Pos -= S.Length;
                    return false;
                }
                Skip_sp();
                return true;
            }
            return false;
        }
        
        public bool Next_symbol(string S)
        {
            int temp_pos = Pos;
            try
            {
                if (Symbol(S)) return true;
            }
            finally { Pos = temp_pos; }
            return false;
        }

        public void Wait(string S)
        {
            if (!Symbol(S)) Err("Waiting for " + S + " but: " + Next20());
        }

        public bool Ending(string S)
        {
            if (Line >= Source.Count) return false;
            string s = Source[Line].TrimEnd();
            int from = s.Length - S.Length;
            if (from < 0) return false;
            return s.Substring(s.Length - S.Length, S.Length) == S;
        }

        public int Remove_comments()
        {
            int in_multi = -1;
            bool old = true;
            if (Source.Count > 0 && Source[0] == "/" + "*" + ")")
                Source[0] = "";
            else
            {
                old = false;
                in_multi = 1;
            }
            for (int i = 0; i <= Source.Count - 1; i++)
            {
                Line = i;
                Pos = 0;
                string s = Source[i].TrimEnd();
                string t = Source[i].Trim();
                
				if (!old)
				{
					if (in_multi < 0 && s.Length > 1 && 
							s[s.Length-2] == '*' && s[s.Length-1] == '/')
					{
						in_multi = i + 1;
					}
					else if (in_multi > 0 && t.Length > 1 && 
							t[0] == '/' && t[1] == '*')
					{
                        s = "";
						in_multi = -1;
					}
				}
	
                if (in_multi < 0 && s.Trim() == "END") return i - 1;
                Source[i] = s;
                if (old && in_multi < 0 && Symbol("(" + "*"))
                {
                    if (!Ending("*" + ")")) in_multi = i+1;
                    Source[i] = "";
                }
                if (in_multi >= 0)
                {
                    if (old && Ending("*" + ")")) in_multi = -1;
                    Source[i] = "";
                }
                else Remove_single_line_comment();
            }
            return Source.Count - 1;
        }

        private void Remove_single_line_comment()
        {
            bool In_str = false;
            string s = Source[Line];
            for (int j = 0; j < s.Length; j++)
            {
                switch (s[j])
                {
                    case '\"': In_str = !In_str; break;
                    case '/': if (!In_str && s[j + 1] == '/')
                        {
                            Source[Line] = s.Substring(0, j);
                            return;
                        }
                        break;
                }
            }
        }

        public void Check_lines_len(int To)
        {
            int i = 0;
            foreach (string s in Source)
            {
                Line = i++;
                if (Line > To) return;
                if (s.Length > 80)
                    Err("Line length exceeds 80 characters");
            }
        }

        public static string Sp(int N)
        {
            string Result = "";
            for (int i = 1; i <= N; i++)
                Result += " ";
            return Result;
        }

        public void Merge_continued(int Last)
        {
            for (int i = Last; i >= 0; i--)
            {
                Line = i;
                string e = Source[i].TrimEnd();
                if (e == "") continue;
                e = e.Substring(e.Length - 1);
                if (".;".Contains(e)) continue;
                if (",([".Contains(e)) // || (Source[i+1].TrimStart() + " ")[0] == '\"')
                {
                    Source[i] += "\r\n" + Source[i + 1];
                    Source[i + 1] = "";
                }
                
                string next = Source[i+1].TrimStart();
                if (next == "" || next[0] == '{' || 
                    next[0] == '<' || next[0] == '[' || next[0] == '(' ||
                    next[0] == '.' || next[0] == ';' ||
                    next[0] == '-' && next[1] == '-') continue;
                    
                if ((next[0] + "").ToLower() == (next[0] + "").ToUpper())
                {
                    Source[i] += "\r\n" + Source[i + 1];
                    Source[i + 1] = "";
                }
            }
            for (int j = 0; j < Source.Count; j++)
            {
                if (j > Last) return;
                string st = Source[j].TrimEnd();
                if (st == "") continue;
                if (!";.".Contains(st[st.Length-1])) continue;
                for (int i = st.Length-2; i >= 0; i--)
                {
                    if (st[i] == st[st.Length-1])
                    {
                        Line = j;
                        Err("Two sequential symbols '" + st[i] + 
                            "' at end of line are not allowed");
                    }
                    if (!" \t".Contains(st[i])) break;
                }
            }
        }

        public string Scan_expr()
        {
            int from = Pos;
            int level = 0;
            for (int xdummy = Pos; xdummy <= Last() + 1; xdummy++)
            {
                Skip_sp();
                if (End_of_line()) break;
                switch (Next_char()[0])
                {
                    case '\"':
                    case '#': string sdummy = String_const(false); break;
                    case '(':
                    case '[':
                    case '{':
                        level++; Skip_char(); break;
                    case ')':
                    case ']':
                    case '}':
                        if (level-- <= 0) goto for_end_label;
                        Skip_char(); break;
                    case '!':
                    case '<':
                    case '>':
                    case '=':
                    case '+':
                    case '-':
                    case '*':
                    case '/':
                    case '%':
                    case '&':
                    case '|':
                    case '^':
                        Skip_char(); break;
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                        double Dummy = Real_number(); break;
                    case ':':
                    case ';':
                    case '.':
                    case ',': if (level <= 0) goto for_end_label;
                        Skip_char();
                        break;
                    case '\'':
                        Skip_char();
                        Ident();
                        Wait("'");
                        break;
                    default: sdummy = Ident();
                        break;
                }
            }
        for_end_label:
            string Result = Source[Line].Substring(from, Pos - from).Trim();
            if (Result == "") Err("Expression is empty");
            return Result;
        }

        public string Scan_array()
        {
            Wait("[");
            string Result = "[" + Scan_expr();
            for (int zdummy = Pos; zdummy <= Last(); zdummy++)
            {
                if (!Symbol(",")) break;
                Result += ", " + Scan_expr();
            }
            Wait("]");
            Result += "]";
            return Result;
        }

        public string Skip_block_comment(List<string> All_id)
        {
            for (int i = Pos; i <= Last(); i++)
            {
                if (Next_char() == "-") Skip_char();
                else break;
            }
            Wait("'");
            int from = Pos;
            Skip_to("'");
            string label = "'" + Source[Line].Substring(from, Pos - from).Trim() + "'";
            Wait("'");
            if (All_id.Contains(label))
                Err("Duplicating label: " + label);
            All_id.Add(label);
            Fin();
            return label;
        }

        /*
        
        METHOD Binary_op|eration ==> {Expr}.{bin} :
            ---- 'binary operation: arithmetic, logic, bitwise or concatenation'
            Skip_sp
            BOOL unk|nown_binary_operation
            RESULT = 'CONCAT'
            CASE Next_char ?
                ["&"]: 
                       CASE Symbol("&&") ? RESULT = 'AND' ==>
                       ELSE RESULT = 'AND_B' ;
                ["|"]: 
                       CASE Symbol("||") ? RESULT = 'OR' ==>
                       ELSE RESULT = 'OR_B' ;
                ["+"]: RESULT = 'PLUS'
                ["-"]: RESULT = 'MINUS'
                ["*"]: RESULT = 'MULT'
                ["/"]: RESULT = 'DIV'
                ["%"]: RESULT = 'REST'
                ["^"]: RESULT = 'XOR_B'
                ["!"]: 
                       CASE Symbol("!=") ? RESULT = 'NOT_EQ' ==>
                       ELSE 
                            CASE Symbol("!IN") ? RESULT = 'NOT_IN' ==>
                            ELSE RESULT = 'NOT_EQ'
                                 unk = TRUE ;
                       ;
                ["="]: RESULT = 'EQUAL'
                       CASE Symbol("==") ? ==>
                       ELSE unk = TRUE ;
                ["<"]: 
                       CASE Symbol("<=") ? RESULT = 'LESS_OR_EQ' ==>
                       ELSE RESULT = 'LESS' ;
                [">"]: 
                       CASE Symbol(">=") ? RESULT = 'GREATER_OR_EQ' ==>
                       ELSE RESULT = 'GREATER' ;
                ["I"]: 
                       CASE Symbol("IN") ? RESULT = 'IN' ==> ;
            ;
            CASE unk ? Err("Unknown operation"._Eunk2 ": " Next20) ;
            CASE RESULT != 'CONCAT' ? Skip_char ; .
        */
        public Expression.Bin Binary_op()
        {
            Expression.Bin Result = Expression.Bin.CONCAT;
            Skip_sp();
            bool unk = false;
            switch ((Next_char() + " ")[0])
            {
                case '&':
                    if (Symbol("&&")) return Expression.Bin.AND;
                    else Result = Expression.Bin.AND_B;
                    break;
                case '|':
                    if (Symbol("||")) return Expression.Bin.OR;
                    else Result = Expression.Bin.OR_B;
                    break;
                case '+': Result = Expression.Bin.PLUS;
                    break;
                case '-': Result = Expression.Bin.MINUS;
                    break;
                case '*': Result = Expression.Bin.MULT;
                    break;
                case '/': Result = Expression.Bin.DIV;
                    break;
                case '%': Result = Expression.Bin.REST;
                    break;
                case '^': Result = Expression.Bin.XOR_B;
                    break;
                case '!':
                    if (Symbol("!=")) return Expression.Bin.NOT_EQ;
                    else if (Symbol("!IN")) return Expression.Bin.NOT_IN;
                    else
                    {
                        Result = Expression.Bin.NOT_EQ;
                        unk = true;
                    }
                    break;
                case '=': Result = Expression.Bin.EQUAL;
                    if (Symbol("==")) return Result;
                    unk = true;
                    break;
                case '<': 
                    if (Symbol("<=")) return Expression.Bin.LESS_OR_EQ;
                else Result = Expression.Bin.LESS;
                    break;
                case '>':
                    if (Symbol(">=")) return Expression.Bin.GREATER_OR_EQ;
                    else Result = Expression.Bin.GREATER;
                    break;
                case 'I':
                    if (Symbol("IN")) return Expression.Bin.IN;
                    //unk = true;
                    break;
            }
            if (unk) Err("Unknown operation: " + Next20());
            if (Result != Expression.Bin.CONCAT) Skip_char();
            return Result;
        }

        public bool End_of_expr()
        {
            Skip_sp();
            if (End_of_line()) return true;
            switch ((Next_char() + " ")[0])
            {
                case ':': case ';': case '.': case ',': case ')': case ']': case '?':
                    return true;
                case '<': return Next_symbol("<<");
                case '>': return Next_symbol(">>");
                case '=': return Next_symbol("==>") || !Next_symbol("==");
                case 'T': return Next_symbol("TO");
                case 'D': return Next_symbol("DOWNTO");
            }
            return false;
        }

        public void Err(string M) { Compiler_.Error(M); }
        public void Warn(string M) { Compiler_.Warning(M); }

        public string Rest_of_line()
        {
            string Result = "";
            if (Line < Source.Count)
            {
                Result = Source[Line];
                if (Pos >= Result.Length) Result = "";
                else Result = Result.Substring(Pos);
            }
            return Result;
        }

        public string Current_line()
        {
            string Result = "";
            if (Line < Source.Count) Result = Source[Line];
            return Result;
        }

        /*
        
        METHOD check_operation({Expr} E|xpression_binary) :
			CASE Ignore_errors ? ==> ;
            -------------------------------------- 'Check by type kinds first'
            {Type}.{kind_type} k1|_argument1 = E.Arg1_.Type_expr.Kind
            {kind_type} k2|_argument2 = E.Arg2_.Type_expr.Kind
            BOOL a1|_array_arg1 = E.Arg1_.Is_array
            BOOL a2|_array_arg2 = E.Arg2_.Is_array
            CASE E.Sign IN ['PLUS', 'MINUS', 'MULT', 'DIV',
            	 'LESS', 'LESS_OR_EQ', 'GREATER', 'GREATER_OR_EQ',
            	 'EQUAL', 'NOT_EQ']
            	 ?
				 CASE ?
                 [k1 == 'INT' && k2 == 'REAL'] ?
                      {Expr} conv|ert_operation = {Expr}(Arg1_ = E.Arg1_,
                     		 Operation = 'INT2REAL',
                     		 Type_obj_ = Compiler_.{Compiler}.TYPE_REAL),
                     		 OWNED BY E
                     		 
                      E.Arg1_ = conv
                      k1 = 'REAL'
                 [k1 == 'REAL' && k2 == 'INT'] ?
                      conv = {Expr}(Arg1_ = E.Arg2_,
                     		 Operation = 'INT2REAL',
                     		 Type_obj_ = Compiler_.{Compiler}.TYPE_REAL),
                     		 OWNED BY E
                     		 
                      E.Arg2_ = conv
                      k2 = 'REAL' ;
            ;
            CASE E.Sign ?
            ['UNDEFINED']: NONE
            ['PLUS', 'MINUS', 'MULT', 'DIV']:
                ------------------------------- '+, -, *, /'
                BOOL ok|_operation = !a1 && !a2
                CASE k1 IN ['CLASS', 'STRUCTURE'] 
                	 || k2 IN ['CLASS', 'STRUCTURE']
                	 ? 
	                 {Fun} operator 
	                 	   = Compiler_.{Compiler}.Current_fun.Search_operator(
	                 	   E.Sign, E.Arg1_, E.Arg2_)
	                 		
	                 ok = operator != NONE
	                 CASE ok ?
	                      operator.Count_ref += 1
	                      operator.Usage_count += 1
	                      Compiler_.{Compiler}.Current_fun.Calls_fun(operator)
	                      E.Operation = 'FUN'
	                      E.Fun_ = operator
	                      E.Items_[] << E.Arg1_ << E.Arg2_
	                 	  E.Type_obj_ = operator.Result.Type_ ;
                ELSE
	                 ok &&= k1 IN ['INT', 'REAL']
	                 ok &&= k2 IN ['INT', 'REAL']
                	 E.Type_obj_ = E.Arg1_.Type_expr
	                 CASE E.Type_obj_.Kind == 'INT' ?
	                     E.Type_obj_ = E.Arg2_.Type_expr ;
                ;
            ['REST', 'AND_B', 'OR_B', 'XOR_B']:
                -------------------------------- '%, &, |, ^'
                ok = k1 == 'INT' && k1 == 'INT' && (
                     !a1 && !a2)
                     
                E.Type_obj_ = Compiler_.{Compiler}.TYPE_INT
            ['LESS', 'LESS_OR_EQ', 'GREATER', 'GREATER_OR_EQ']:
                -------------------------------- '<, <=, >, >='
                ok = k1 IN ['INT', 'REAL'] && k2 IN [
                            'INT', 'REAL']
                            
                ok ||= k1 == k2 && k1 == 'STR'
                ok &&= !a1 && !a2
                E.Type_obj_ = Compiler_.{Compiler}.TYPE_BOOL
            ['EQUAL', 'NOT_EQ']:
                -------------------------------- '==, !='
                ok = k1 == 'INT' && k2 == 'INT'
                ok ||= k1 == k2
                CASE !ok && k1 IN ['CLASS', 'STRUCTURE'] 
                	 && k2 == 'CLASS'
                	 && E.Arg2_.Operation == 'CONST'
                	 && E.Arg2_.Var_arg.Constant_value == "NONE"
                	 ?
                     ok = TRUE ;
                ok &&= !a1 && !a2
                ok &&= k1 != 'REAL' && k2 != 'REAL'
                CASE k1 == 'ENUM' ?
                     {Type} t1|_type_of_arg1 = E.Arg1_.Type_expr 
                     {Type} t2|_type_of_arg2 = E.Arg2_.Type_expr 
                     ok = t1.Names[0] == t2.Names[0]
                     ok &&= t1.Names[1] == t2.Names[1]
                     ok &&= t1.Base_ == t2.Base_
                ;
                E.Type_obj_ = Compiler_.{Compiler}.TYPE_BOOL
            ['IN', 'NOT_IN']:
                --------------------------------- 'IN - check now'
                ok = k1 == 'STR' && k1 == k2 && !a1 && !a2
                ok ||= !a1 && a2 && k1 == k2 && k2 != 'REAL'
                CASE k1 == 'STR' && a2 && E.Arg2_.Operation == 'ARRAY' && (
                     E.Arg2_.Items_[].Count == 1)
                     ?
                     Err("Don't use IN/!IN to check a string for a certain "
                     "value, use ==/!= in such case"._Edont)
                ;
                CASE k1 == 'ENUM' ?
                     STR name1|_type = E.Arg1_.Type_expr.Names[*]
                     STR name2|_type = E.Arg2_.Type_expr.Names[*]  
                     ok &&= name1 == name2
                     CASE ok ?
                          {Type} base1|_type = E.Arg1_.Type_expr.Base_
                          {Type} base2|_type = E.Arg2_.Type_expr.Base_  
                          ok &&= base1 == base2 ; 
                ;
                E.Type_obj_ = Compiler_.{Compiler}.TYPE_BOOL
            ['LIKE', 'UNLIKE']:
                ok = k1 == 'STR' && k1 == k2 && !a1 && !a2
                E.Type_obj_ = Compiler_.{Compiler}.TYPE_BOOL
            ['OR', 'AND']:
                ---------------------------------- '||, &&'
                ok = k1 == k2 && k1 == 'BOOL' && !a1 && !a2
                E.Type_obj_ = Compiler_.{Compiler}.TYPE_BOOL
            ['CONCAT']:
                ---------------------------------- 'strings concatenation'
                ok = k1 == k2 && k1 == 'STR' && !a1 && !a2
                E.Type_obj_ = Compiler_.{Compiler}.TYPE_STR
                CASE !ok && k1 != 'STR' ?
                     STR add|itionally = ", concatenation of not string "
                     "argument"
                ;
                CASE !ok && add == "" && k2 != 'STR' ?
                     add = ", concatenation with not string argument" ;
            ;
            CASE !ok ?
                PUSH Pos = E.Arg1_.At_pos :
                     CASE add != "" ?
                          Err("Inapplicable operation"._Einapp 
                          "(" add "): " Next20)
                     ELSE Err("Inapplicable operation"._Einapp2 ": " Next20) ;
                ;
            ; .
            
FUN bool2str( BOOL B|ool_value) ==> STR :
    CASE B ? RESULT = "TRUE" ==> ;
    RESULT = "FALSE" .
            
            
        TEST checking_operation :
             ------------------------------------------ 'Let`s begin'
             ASSERT bool2str(FALSE) == "FALSE"
             ASSERT bool2str(TRUE) == "TRUE"
             << "TEST Parser.checking_operation ----------------------------"#NL
             ASSERT TRUE
             ------------------------------------------ 'prepare'
             c|lass_temp = {Type}(Names[] << "{Test:Parser}")
             com|piler_temp = {Compiler}(Current_class = c, Max_err=20)
             p|arser_temp = {Parser}(Compiler_ = com, Class_ = c)
             c.Parse = p
             Init_system_types(com)
             ASSERT TRUE
             ---------------------------------------- 'prepare system constants'
             vtemp|_variable = {Var}(Line = 0, In_type_ = c)
             com.Init_const(p)
             ASSERT vtemp != NONE
             ------------------------------------------ 'start testing'
             ASSERT com.Errors_count == 0
             p.Test_line("")
             ------------------------------- 'check_operation'
             a1|_argument = {Expr}(
             		Operation = 'VAR',
                    Var_arg = {Var}(
                    		Line = 0,
                    		Type_ = com.TYPE_INT)
             ), OWNED BY c
                    
             a2|_argument = {Expr}(
             		Operation = 'VAR',
                    Var_arg = {Var}(
                    		Line = 0,
                    		Type_ = com.TYPE_INT)
             ), OWNED BY c
                    
             ex|pression_temp = {Expr}(
             		Operation = 'BINARY',
             		Sign = 'PLUS',
                	Arg1_ = a1,
                	Arg2_ = a2)
                
             ASSERT com.Errors_count == 0
             ------------------------------- '[ + ]'
             com.Errors_count = 0
             p.check_operation(ex)
             ASSERT com.Errors_count == 0
             PUSH a1.Var_arg.Type_ = com.TYPE_STR :
                  p.check_operation(ex)
                  ASSERT com.Errors_count == 1 ;
             ------------------------------ '[ < ]'
             ex.Sign = 'REST'
             p.check_operation(ex)
             ASSERT com.Errors_count == 1
             ------------------------------ '[ % ]'
             ex.Sign = 'LESS'
             p.check_operation(ex)
             ASSERT com.Errors_count == 1
             ------------------------------ '[ == ]'
             ex.Sign = 'EQUAL'
             p.check_operation(ex)
             ASSERT com.Errors_count == 1
             ------------------------------ '[ IN ]'
             ex.Sign = 'IN'
             p.check_operation(ex)
             ASSERT com.Errors_count == 1
             ------------------------------ '[ IN ["xxx"] ]'
             << "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" #NL
             
             temp_const = {Expr}(Operation = 'CONST',
                    Var_arg = {Var}(Type_ = com.TYPE_STR, Line = 0))
                    
             temp_a2|_IN_only = {Expr}(Operation = 'ARRAY',
                Items_[] << temp_const), OWNED BY c
                
             temp_a1|_IN_only = {Expr}(Operation = 'VAR',
                    Var_arg = {Var}(Line = 0, Type_ = com.TYPE_STR)),
                    OWNED BY c
                    
             PUSH ex.Arg2_ = temp_a2 :
             PUSH ex.Arg1_ = temp_a1 :
                  p.check_operation(ex) ;
             ;
             ASSERT com.Errors_count >= 1
             com.Errors_count = 1
             ------------------------------ '[ OR ]'
             p.Test_line("")
             ex.Sign = 'OR'
             p.check_operation(ex)
             ASSERT com.Errors_count == 2
             ------------------------------ '[ CONCAT ]'
             p.Test_line("")
             ex.Sign = 'CONCAT'
             p.check_operation(ex)
             ASSERT com.Errors_count == 3
             PUSH a1.Var_arg.Type_ = com.TYPE_STR :
                  p.check_operation(ex)
                  ASSERT com.Errors_count == 3, com.Errors_count.Str ;
             ----------------------------- 'NAN =='
             com.Errors_count = 0
             ex.Sign = 'EQUAL'
             PUSH ex.Arg1_.Var_arg = com.NAN_c :
                  p.check_operation(ex)
                  ASSERT com.Errors_count >= 0 ;
             ------------------------------ 'compare enums'
             com.Errors_count = 0
             {Type} en|um_temp = {Type}(Kind = 'ENUM')
             
             a1 = {Expr}(Operation = 'VAR', Var_arg = {Var}(Line = 0, 
                Type_ = en)), OWNED BY c
                
             a2 = {Expr}(Operation = 'VAR', Var_arg = {Var}(Line = 0, 
                Type_ = en)), OWNED BY c
                
             ex = {Expr}(Operation = 'BINARY', Sign = 'EQUAL', Arg1_ = a1,
                Arg2_ = a2), OWNED BY c
                
             p.check_operation(ex)
             ASSERT com.Errors_count == 0
             ------------------------------ 'check enum IN [enums]'
             ex.Sign = 'IN'
             a2.Var_arg.Is_array = TRUE
             p.check_operation(ex)
             ASSERT com.Errors_count == 0
             //------------------------------ 'test priorities'
             //{Expr} operands|_temp[] << a1 << a2
             //{bin} signs|_temp[] << 'OR'
             //p.Do_priorities([" "], operands[], signs[])
             //p.Do_priorities(["*"], operands[], signs[])
             //p.Do_priorities(["+"], operands[], signs[])
             //p.Do_priorities(["^"], operands[], signs[])
             //ASSERT TRUE
             //-------------------------------- '... + comparisons & logic'
             //p.Do_priorities(["|"], operands[], signs[])
             //p.Do_priorities(["I"], operands[], signs[])
             //p.Do_priorities(["<"], operands[], signs[])
             //p.Do_priorities(["&&"], operands[], signs[])
             //p.Do_priorities(["||"], operands[], signs[])
             
             //ASSERT operands[].Count == 1, operands[].Count.Str
             //   ", " operands[0].Operation.Name
             //   ", " operands[1].Operation.Name
             //--------------------------------- 'should stay one operand on top'
             //ASSERT signs[].Count == 0, signs[].Count.Str ", " signs[0].Name
             //<< "end of TEST Parser.checking_operation ---------------------"#NL
             .
        */
        static int CNT_check_operation;

        private void check_operation(Expression E)
        {
            if (CNT_check_operation++ == 7)
            { }

            Type.Kind_type k1 = E.Arg1_.Type_expr() != null ? E.Arg1_.Type_expr().Kind : Type.Kind_type.BOOL;
            Type.Kind_type k2 = E.Arg2_ != null && E.Arg2_.Type_expr() != null ? E.Arg2_.Type_expr().Kind : Type.Kind_type.BOOL;
            bool a1 = E.Arg1_.Is_array();
            bool a2 = E.Arg2_ != null ? E.Arg2_.Is_array() : false;
            bool ok = false;
            switch (E.Sign)
            {
                case Expression.Bin.PLUS:
                case Expression.Bin.MINUS:
                case Expression.Bin.MULT:
                case Expression.Bin.DIV:
                    ok = k1 == Type.Kind_type.INT || k1 == Type.Kind_type.REAL;
                    ok = ok && (k2 == Type.Kind_type.INT || k2 == Type.Kind_type.REAL);
                    ok = ok && !a1 && !a2;
                    E.Type_obj_ = E.Arg1_.Type_expr();
                    if (E.Type_obj_.Kind == Type.Kind_type.INT)
                        E.Type_obj_ = E.Arg2_.Type_expr();
                    break;
                case Expression.Bin.REST:
                case Expression.Bin.AND_B:
                case Expression.Bin.OR_B:
                case Expression.Bin.XOR_B:
                    ok = k1 == k2 && k1 == Type.Kind_type.INT && !a1 && !a2;
                    E.Type_obj_ = Compiler_.TYPE_INT;
                    break;
                case Expression.Bin.LESS:
                case Expression.Bin.LESS_OR_EQ:
                case Expression.Bin.GREATER:
                case Expression.Bin.GREATER_OR_EQ:
                    ok = (k1 == Type.Kind_type.INT || k1 == Type.Kind_type.REAL) &&
                        (k2 == Type.Kind_type.INT || k2 == Type.Kind_type.REAL) ||
                        k1 == k2 && k1 == Type.Kind_type.STR;
                    ok = ok && !a1 && !a2;
                    E.Type_obj_ = Compiler_.TYPE_BOOL;
                    break;
                case Expression.Bin.EQUAL:
                case Expression.Bin.NOT_EQ:
                    ok = (k1 == Type.Kind_type.INT) &&
                        (k2 == Type.Kind_type.INT) ||
                        k1 == k2;
                    ok = ok && (k1 != Type.Kind_type.REAL) && (
                         k2 != Type.Kind_type.REAL) && !a1 && !a2;
                    E.Type_obj_ = Compiler_.TYPE_BOOL;
                    break;
                case Expression.Bin.IN:
                case Expression.Bin.NOT_IN:
                    ok = k1 == Type.Kind_type.STR && k1 == k2 && !a1 && !a2 ||
                        !a1 && a2 && k1 == k2;
                    E.Type_obj_ = Compiler_.TYPE_BOOL;
                    break;
                case Expression.Bin.OR:
                case Expression.Bin.AND:
                    ok = k1 == k2 && k1 == Type.Kind_type.BOOL && !a1 && !a2;
                    E.Type_obj_ = Compiler_.TYPE_BOOL;
                    break;
                case Expression.Bin.CONCAT:
                    ok = k1 == k2 && k1 == Type.Kind_type.STR && !a1 && !a2;
                    E.Type_obj_ = Compiler_.TYPE_STR;
                    break;
            }
            if (!ok)
            {
                int save_pos = Pos;
                Pos = E.Arg1_.At_pos;
                Err("Unapplicable operation: " + Next20());
                Pos = save_pos;
            }
        }

        /*
        
        BOOL Ignore_errors
        
        METHOD Do_priorities(
            {Expr} Operands|_array[], {E_op} Operations[])
            :
            ---------- 'Build tree downing all operations with certain priority'
            FOR i IN [0 TO Operations[].Count-1] :
                CASE Operations[].Count==0 ? BREAK i ;
                INT max_pr|irity = 0
                FOR j IN [0 TO Operations[].Count-1] :
                    CASE max_pr < Operations[j].Priority ? 
                         max_pr = Operations[j].Priority ;
                ;
                CASE !priority(max_pr, Operands[], Operations[]) ? BREAK i ;
            ; .
            
        */
        public void Do_priorities(string[] Sets, List<Expression> Operands, List<Expression.Bin> Signs)
        {
            foreach (string s in Sets)
            {
                int Nmax = Operands.Count;
                for (int x = 0; x <= Nmax; x++)
                    if (!priority(s, Operands, Signs)) break;
            }
        }

        /*
        //BOOL flag_optimize|_test
        
        METHOD priority( INT Min_pr|iority_to_down,
            {Expr}  Operands|_array[],
            {E_op}  Operations[]) ==> BOOL
            :
            ---------------- 'Build tree downing operations of certain priority'
            INT at|_position = 0
            FOR i IN [0 TO Operations[].Count-1] :
                CASE at >= Operations[].Count ? ==> ;
                {E_op} op|eration_at_i_op = Operations[at]     
                BOOL want|_move = op.Priority >= Min_pr
                CASE !want ?
                     at += 1
                     CONTINUE i ;
  
				---------------------- 'want move down as having more priority'
				{Expr} o1|_operand = Operands[at]
				
				{Expr} down|ed_operation = {Expr}(Operation = 'BINARY',
							 Arg1_ = o1, Arg2_ = Operands[at+1],
							 Sign = Operations[at].Sign, At_line = o1.At_line,
							 At_pos = o1.At_pos), OWNED BY THIS //Operands[at]

						
				check_operation(down)
				----------------------- 'optimize compare sub-strings'
				{Expr} a1|_argument_of_down = down.Arg1_
				{Expr} a2|_argument_of_down = down.Arg2_
				BOOL optimized = FALSE
				CASE a1.Operation == 'INDEX' ?
				     BOOL is_substr|ing = a1.Type_expr.Kind == 'STR' 
				     	  is_substr &&= ! a1.Is_array
				     	  is_substr &&= a1.Arg3_ != NONE
                     CASE is_substr ?
					 	  optimize_comparing_substring(down) 
					 	  optimized = TRUE ;
				;
				CASE !optimized ?
				     is_substr = !a2.Is_array 
				     is_substr &&= a2.Operation == 'INDEX'
				     is_substr &&= a2.Type_expr.Kind == 'STR'
				     is_substr &&= a2.Arg3_ != NONE
				     CASE is_substr ?
					 	  optimize_comparing_substring(down) ;
				;
				----------------------- 'move down'
				Operands[at] = down
				Operands[].Delete(at+1)
				Operations[].Delete(at)
				RESULT = TRUE
				----------------------- 'long concatenations'
				CASE down.Sign != 'CONCAT' ? CONTINUE i ;
				down.Items_[] << down.Arg1_ << down.Arg2_
				down.Arg1_ = NONE
				down.Arg2_ = NONE
				FOR j|dummy IN [at+1 TO Operands[].Count-1] :
					CASE Operations[at].Sign != 'CONCAT' ? ==> ;
					BOOL is_strings = Operands[at+1].Type_expr.Kind == 'STR'
						 is_strings &&= !Operands[at+1].Is_array
                    CASE !is_strings && !Ignore_errors ?
                         PUSH Pos = Operands[at+1].At_pos :
                              Err("Concatenation is not applicable for a not "
                              "string operand"._concat ": " Next20) ;
                    ;
                    down.Items_[] << Operands[at+1]
                    Operands[].Delete(at+1)
                    Operations[].Delete(at)
				;
            ; .
        
			
		FUN Test_parser|_with_temp_compiler ==> {Parser},
			NEW
            :
			--------------------------------- 'compiler -> class -> parser'
			c|ompiler_temp = {Compiler}
            t|ype_class_temp = {Type}
            RESULT = {Parser}(Compiler_ = c, Class_ = t)
            c = {Compiler}, OWNED BY RESULT
            RESULT.Compiler_ = c
            t.Parse = RESULT
            --------------------------------- 'initialize constants'
            v|ariable_temp = {Var}(Line = -1, In_type_ = t)
            c.Init_system_types
            c.Init_const(t.Parse)
			--------------------------------- 'current positions'
			c.Current_class = t
			
			f|unction_test = {Fun}(Names[] << "Dummy", A_Name_1 = "Dummy",
				Parse = RESULT), >> t.Method[]
				
			c.Current_fun = f
			.
            
        //TEST of_long_concatenation :
        //     {Parser} p|arse_temp = Test_parser
        //     {Compiler} c|ompiler = p.Compiler_.{Compiler}
        //     ASSERT p.Source[].Count == 0
        //     --------------------------------- 'prepare expression to optimize'
        //     s0|_string_temp = {Expr}(
        //            Operation = 'CONST',
        //            Var_arg = c.EMPTY_str)
        //        
        //     b0|_boolean_temp = {Expr}(
        //            Operation = 'CONST',
        //            Var_arg = c.TRUE_c)
        //        
        //     {Expr} operand|s_array[] << s0 << s0 << s0 << s0 << b0
        //     {bin} signs|_array[] << 'IN' << 'CONCAT' << 'CONCAT' << 'EQUAL'
        //     ASSERT p.priority("", operand[], signs[])
        //     ----------------------------------------------- 'checks'
        //     ASSERT operand[].Count == 3
        //     ASSERT operand[0] == s0
        //     ASSERT operand[1].Items_[].Count == 3
        //     .
            
        METHOD optimize_comparing_substring(
            {Expr} C|ompare_operation_optimize)
            :
            CASE C.Sign !IN ['LESS', 'LESS_OR_EQ', 'GREATER', 'GREATER_OR_EQ',
	        	 'EQUAL', 'NOT_EQ'] 
	        	 ? ==> ;

            {Expr} a1|_argument = C.Arg1_
            {Expr} a2|_argument = C.Arg2_
            CASE a1.Operation == 'INDEX' && a2.Operation == 'INDEX' ?
                 f|unction_special = {Expr}(Operation = 'SYSTEM_FUN', 
                    At_line = a1.At_line, At_pos = a1.At_pos,
                    Fun_ = Compiler_.{Compiler}.SYSFUN_Compare_substrings,
                    Items_[] << a1.Arg1_ << a2.Arg1_,
                    Items_[] << a1.Arg2_ << ensure_2nd(a1.Arg3_, a1.Arg2_),
                    Items_[] << a2.Arg2_ << ensure_2nd(a2.Arg3_, a2.Arg2_)), 
                    OWNED BY C
                 C.Arg1_ = f
                 
                 C.Arg2_ = {Expr}(Operation = 'CONST',
                    Var_arg = Compiler_.{Compiler}.N0_c ), OWNED BY C ==>
            ;
            CASE a1.Operation == 'INDEX' ?
                 f = {Expr}(Operation = 'SYSTEM_FUN',
                    At_line = a1.At_line, At_pos = a1.At_pos,
                    Fun_ = Compiler_.{Compiler}.SYSFUN_Compare_substr1,
                    Items_[] << a1.Arg1_ << a1.Arg2_ << a1.Arg3_ << a2),
                    OWNED BY C
                 C.Arg1_ = f
                 
                 C.Arg2_ =  {Expr}(Operation = 'CONST',
                    Var_arg = Compiler_.{Compiler}.N0_c ), OWNED BY C ==>
            ;
            
            f = {Expr}(Operation = 'SYSTEM_FUN',
               At_line = a1.At_line, At_pos = a1.At_pos,
               Fun_ = Compiler_.{Compiler}.SYSFUN_Compare_substr1,
               Items_[] << a2.Arg1_ << a2.Arg2_ << a2.Arg3_ << a1),
               OWNED BY C
               
            CASE C.Sign.Name ?
            ["'LESS'"]: C.Sign = 'GREATER'
            ["'GREATER'"]: C.Sign = 'LESS'
            ["'LESS_OR_EQ'"]: C.Sign = 'GREATER_OR_EQ'
            ["'GREATER_OR_EQ'"]: C.Sign = 'LESS_OR_EQ' ;
            C.Arg1_ = f
            
            C.Arg2_ =  {Expr}(Operation = 'CONST',
               Var_arg = Compiler_.{Compiler}.N0_c ), OWNED BY C .
            
              
        //TEST of_optimize_substring_compare :
        //     --------------------------------- 'prepare an environment'
        //     c|ompiler_temp = {Compiler}
        //     t|ype_class_temp = {Type}
        //     p|arse_temp = {Parser}(Compiler_ = c, Class_ = t)
        //     t.Parse = p
        //     ASSERT t.Parse == p
        //     --------------------------------- 'initialize constants'
        //     v|ariable_temp = {Var}(Line = -1, In_type_ = t)
        //     c.Init_system_types
        //     //v.Init_const
        //     c.Init_const(p)
        //     ASSERT c.TYPE_STR != NONE
        //     --------------------------------- 'prepare expression to optimize'
        //     x|_operand_temp = {Expr}(
        //            Operation = 'VAR',
        //            Var_arg = {Var}(
        //                    Line=0,
        //                    Names[] << "x",
        //                    Type_ = c.TYPE_STR))
        //        
        //     n0|_number_temp = {Expr}(Operation = 'CONST', Var_arg = c.N0_c)
        //     n1|_number_temp = {Expr}(Operation = 'CONST', Var_arg = c.N1_c)
        //     
        //     {Expr} operands|_temp[] << {Expr}(Operation = 'INDEX', 
        //        Arg1_ = x, Arg2_ = n0, Arg3_ = n1)
        //        
        //     operands[] << {Expr}(Operation = 'INDEX', 
        //        Arg1_ = x, Arg2_ = n0, Arg3_ = n1)
        //        
        //     ASSERT operands[].Count == 2
        //     ---------------------------------- 'test optimizing 0'
        //     {bin} signs|_array[] << 'LESS'
        //     ASSERT p.priority("<", operands[], signs[])
        //     ---------------------------------- 'test optimizing 0 (2)'
        //     operands[].Clear
        //     
        //     operands[] << {Expr}(Operation = 'INDEX', 
        //        Arg1_ = x, Arg2_ = n0, Arg3_ = n1) << x
        //        
        //     signs[].Clear
        //     signs[] << 'GREATER'
        //     ASSERT p.priority("<", operands[], signs[])
        //     ---------------------------------- 'test optimizing 1 - 5'
        //     BOOL ok|_optimizing = TRUE
        //     FOR i IN [1 TO 5] :
        //         operands[].Clear
        //         
        //         operands[] << x << {Expr}(
        //                Operation = 'INDEX', 
        //                Arg1_ = x,
        //                Arg2_ = n0,
        //                Arg3_ = n1)
        //                
        //         signs[].Clear
        //         CASE i ?
        //         [1, 2]: signs[] << 'LESS'
        //         [3]: signs[] << 'LESS_OR_EQ'
        //         [4]: signs[] << 'GREATER'
        //         [5]: signs[] << 'GREATER_OR_EQ' ;
        //         CASE ! p.priority("<", operands[], signs[]) ? ok = FALSE ;
        //     ;
        //     ASSERT ok, "not all calls to compare priority were ok"
        //     .
             
        FUN ensure_2nd|_operand_get_first_if_NONE(
            {Expr} A2|_argument_shuold_be_returned_if_OK,
            {Expr} A3|_argument_return_if_A2_is_NONE ) ==> {Expr}
            :
            --------------------------------------- 'either A2 or A3'
            CASE A2 != NONE ? RESULT = A2 ==> ;
            RESULT = A3 .
            
        TEST of_ensure_2nd :
             e2|_temporary = {Expr}
             e3|_temporary = {Expr}
             ASSERT ensure_2nd(NONE, e3) == e3
             ASSERT ensure_2nd(e2, e3) == e2 .
             
        */
        private bool priority(string Set, List<Expression> Operands, List<Expression.Bin> Signs)
        {
            for (int i = 0; i < Signs.Count; i++)
            {
                bool want = false;
                Expression.Bin sgn = Signs[i];
                switch (Set[0])
                {
                    case '*': want = sgn == Expression.Bin.MULT ||
                        sgn == Expression.Bin.DIV || sgn == Expression.Bin.REST;
                        break;
                    case '+': want = sgn == Expression.Bin.PLUS || sgn == Expression.Bin.MINUS;
                        break;
                    case '^': want = sgn == Expression.Bin.AND_B || sgn == Expression.Bin.XOR_B;
                        break;
                    case '|':
                        if (Set == "|") want = sgn == Expression.Bin.OR_B;
                        else want = sgn == Expression.Bin.OR;
                        break;
                    case '<': want = sgn == Expression.Bin.LESS || sgn == Expression.Bin.LESS_OR_EQ ||
                        sgn == Expression.Bin.GREATER || sgn == Expression.Bin.GREATER_OR_EQ ||
                        sgn == Expression.Bin.EQUAL || sgn == Expression.Bin.NOT_EQ;
                        break;
                    case 'I': want = sgn == Expression.Bin.IN || sgn == Expression.Bin.NOT_IN;
                        break;
                    case '&': want = sgn == Expression.Bin.AND;
                        break;
                    default: want = sgn == Expression.Bin.CONCAT;
                        break;
                }
                if (want)
                {
                    Expression down = new Expression();
                    down.Operation = Expression.Operation_kind.BINARY;
                    down.Sign = Signs[i];
                    down.Arg1_ = Operands[i];
                    down.Arg2_ = i < Operands.Count - 1 ? Operands[i + 1] : null;
                    down.Copy_location(down.Arg1_);
                    check_operation(down);
                    Operands[i] = down;
                    if (i < Operands.Count - 1) { Operands.RemoveAt(i + 1); }
                    Signs.RemoveAt(i);
                    return true;
                }
            }
            return false;
        }

    }
}
/*
END
*/


