//==============================================================================
//  process libraries and scopes
//==============================================================================
unit LibrariesAndScopes;

//==============================================================================
//  INTERFACE
//==============================================================================
interface

procedure parse();
procedure writeInOrder(var Result:string);

//==============================================================================
//  IMPLEMENTATION
//==============================================================================
implementation
uses Windows, SysUtils, TextOnly, JassLib, Structs, StringHash, vxFilesys,
     PseudoRandom, JASSerException, Zinc, ScopeStack, JStruct,
     Constants, ExternalUsage, TextMacro, JHInterface, JHInput, JHParams,
     ImportPaths, TextCommon, PreprocessorImport, TempUnit, InitializerFunctions;


var
    removedLibrariesN:integer;
    removedLibraries: array of string;
    libraries: array of TLibrary;
    library_usedinit:boolean=false;
    libraryn:integer;
    ScopeInit:array of string;
    ScopeName:array of string;
    ScopeInitN:integer;

    //ln:integer;
    //input: array of string;
    globals: string='';
    premainarea: string;
    postmainarea: string;

//==============================================================================
procedure GetRealLine(var i:integer; var line:string );
var
 j,L,anchor, strcount:integer;
 ctl,str,raw:boolean;

 c:char;


begin
    //line:='';
    ctl:=false;
    str:=false;
    raw:=false;

    { lots of threats, for example "aaa
     aa f f"  is a valid string, also "aaa\"aaa" and "aaadf\\"

      There is also the infamous '"' which does not open an string
     }
    anchor:=i;

    strcount:=0;
    while (i<=inputN) do begin

        j:=1;
        L:=Length(JHInput.Get(i));
        while j<=L do begin
            c:=JHInput.Get(i)[j];

            if (str) then begin
                strcount:=strcount+1;
                if (ctl) then ctl:=false
                else if (c='"') then begin
                    str:=false;
                    strcount:=0;
                end
                else if (c='\') then ctl:=true;
                if (strcount>1100) then begin
                    raise JASSerLineException(anchor,'String literal size limit exceeded /unclosed string');
                end;

            end else if (raw) then begin
                if (c='''') then raw:=false;

            end else if ( (c='/') and (j<L)   and (JHInput.Get(i)[j+1]='/')) then begin
                //found a comment, end.
                j:=L;

            end else if (c='"') then begin
                 str:=true;
                 strcount:=0;
            end
            else if (c='''') then raw:=true;

            j:=j+1;
        end;

        if (i<>anchor) then Insert(JHInput.Get(i),line, VERYBIG );

        if (str) then begin
            //doh, string didn't end the line yet;
            i:=i+1;
            Insert('\n',line, VERYBIG ); //It is handy and prevents pjass from returning the wrong line number


        end
        else break;
    end;
    if (str) then raise JASSerLineException(anchor,'Unclosed string');

end;

//==============================================================================
//  Among the libraries with no requirements left, find the one with smallest name
//==============================================================================
function findNoReqLibrary:integer;
var i:integer;
begin
    Result:=-1;
    for i := 0 to libraryn - 1 do if (libraries[i].reqn=0) then begin
        if(Result=-1) or (CompareStr(Libraries[i].name, Libraries[result].name)<0) then begin
            Result:=i;
        end;
    end;

end;

//==============================================================================
//  Throws exception if library cycle is found.
//==============================================================================
procedure FindLibraryCycles;
var
   i:integer;
   processed,parent: array of integer;

     function buildCycle(x:integer; par:integer):string;
     var
        i,c,y:integer;
        arr: array of string;
     begin
         c:=2;
         y:=par;
         while( y<>x) do begin
             c:=c+1;
             y:=parent[y];
         end;
         SetLength(arr,c);
         arr[c-1]:=libraries[x].name;
         arr[c-2]:=libraries[par].name;
         y:=par;
         for i := c-3 downto 0 do begin
             y:=parent[y];
             arr[i]:=libraries[y].name;
         end;
         result:=arr[0];
         for i := 1 to c-1 do
             result:=result+' -> '+arr[i];
           

     end;


     procedure dfs(x:integer; par:integer);
     var j,k:integer;
     begin
         if(processed[x]=0) then begin
             processed[x]:=2;
             parent[x]:=par;
             for j := 0 to Libraries[x].reqn-1 do begin
                 k:=0;
                 while(k<libraryn) do begin
                     if(libraries[k].name=libraries[x].req[j]) then break;
                     k:=k+1;
                 end;
                 if(k<libraryn) then
                     dfs(k, x);
             end;

             processed[x]:=1;
         end else if(processed[x]=2) then begin
             //cycle!
             raise JasserLineDoubleException(
             libraries[x].declaration,'Found a library requirements cycle.',
             libraries[par].declaration,'\--- '+buildCycle(x,par)
             );
         end;
     end;
begin
    SetLength(processed, libraryn);
    SetLength(parent, libraryn);
    for i := 0 to libraryn - 1 do
        processed[i]:=0;
    for i := 0 to libraryn - 1 do
        if(processed[i]=0) then
            dfs(i,-1);

end;


//==============================================================================
//  remove library from libraries array and insert it into removedLibraries array
//==============================================================================
procedure removeLibrary(i:integer);
var
   name:string;
   lib:TLibrary;
begin
   lib:=Libraries[i];
   name:=lib.name;
   libraries[i]:=libraries[libraryn-1];
   libraryn:=libraryn-1;
   lib.Destroy; 

   if( Length(RemovedLibraries)=removedLibrariesN) then begin
       SetLength(RemovedLibraries,removedLibrariesN*2+1);
   end;
   removedLibraries[removedLibrariesN]:=name;
   removedLibrariesN:=removedLibrariesN+1;
end;

//==============================================================================
//  remove already outputed libraries from requirement lists
//==============================================================================
procedure cleanAddedLibraries;
var   j,k,p,i:integer;
begin
   for j := 0 to libraryn - 1 do begin
      k:=0;
      while(k<libraries[j].reqn) do begin
          p:=-1;
          i:=0;
          while (i<removedLibrariesN) do begin
              if(libraries[j].req[k]=removedLibraries[i]) then begin
                  p:=1;
                  break;
              end;
              i:=i+1;
          end;
          if(p<>-1) then begin
              libraries[j].reqn:=libraries[j].reqn-1;
              libraries[j].req[k]:=libraries[j].req[libraries[j].reqn] ;
          end else k:=k+1;
      end;
   end;
   removedLibrariesN:=0;
end;

//==============================================================================
function getInvolvedLibraries:string;
var
 i,j:integer;
begin

   for i := 0 to libraryn - 1 do begin
       Result:=Result+' '+libraries[i].name+'{';
       for j := 0 to libraries[i].reqn - 1 do Result:=Result+' '+libraries[i].req[j];
       Result:=Result+'}';


   end;


end;

//==============================================================================
// requires a,b,c,d,
procedure parseLibrary(var line:string; var lib:TLibrary; beginp:integer; linenumber:integer; const libraryname:string);
var
   j,k:integer;
   word:string;
   optional:boolean;
begin
    lib.reqn:=0;
    lib.declaration:=linenumber;



    lib.name:=libraryname;
    GetLineWord(line,word,j,beginp);
    lib.validatedinit:=true;
    if (word='initializer') then begin
        GetLineToken(line,word,j,j);
        lib.Init:=word;
        library_usedinit:=true;
        if(not IsWhiteSpace(line,j)) then
            GetLineWord(line,word,j,j) //So requires works later
        else
            word:='';
        lib.validatedinit:=false;
    end else lib.Init:='';

    if ((word='needs')or(word='requires')or(word='uses')) then begin

        //parse requirements
        word:='';
        k:=Length(line);
        while (j<=k) do begin
            if(isWhiteSpace(line,j)) then break;

             GetLineToken(line,word,j,j);
             if(word='') then break;
             if(word='initializer') then begin
                  raise JASSerLineException(linenumber,'Please place initializer before the requirements.');
             end;
             optional := (word='optional');
             if(optional) then begin
                 GetLineToken(line, word, j, j);
             end;

             if ( Length(word)>=2) and (word[1]='/') and (word[2]='/') then break;
             lib.reqn:= lib.reqn +1;
             SetLength(lib.req,lib.reqn);
             SetLength(lib.req_opt,lib.reqn);
             lib.req[lib.reqn-1]:=word;
             lib.req_opt[lib.reqn-1] := optional;
        end;
        if (lib.reqn=0) then raise JASSerLineException(linenumber,'Missing requirements but requires keyword used : '+lib.name);

    end else if ( Length(word)>=2) and (word[1]='/') and (word[2]='/') then begin
       //comment
    end else if (word<>'') then raise JASSerLineException(linenumber,'Not a valid library declaration: ['+word+']?');
end;

//==============================================================================
procedure ReplaceTokens(var hash:TStringHash; var currenthash:TStringHash; var stack:TScopeStack; var s:string; deleteprivate:boolean);
var
  o,token:string;
  i,L,k,x,d:integer;
  str,ctr,now:boolean;

    procedure replacetoken;
    begin
        x:=hash.ValueOf(token);
        if (x>0) then begin
            d:=(x-1) div 2;
            if (x mod 2=0) then //it is public
                stack.makepublic(d,token,currenthash.valueof(token) )
            else
                stack.makeprivate(d,token,currenthash.valueof(token));
        end else if (token='SCOPE_PREFIX') then token:=stack.makeprefix(false)
        else if( token='DEBUG_MODE') then begin
            if(DEBUG_MODE()) then
                token:='true'
            else
                token:='false';
        end else if (token='SCOPE_PRIVATE') then token:=stack.makeprefix(true);


    end;
begin
    o:=s;
    s:='';
    i:=1;
    str:=false;
    ctr:=false;
    now:=false;
    k:=1;
    L:=Length(o);
    while (i<=L) do begin
        if (str) then begin
            if (ctr) then ctr:=false
            else begin
                if (o[i]='"') then begin
                     str:=false;
                     k:=i+1;
                end else if (o[i]='\') then ctr:=true;
            end;

            Insert(o[i],s,VERYBIG);

        end else if (o[i]='"') then begin
            if (k<>i) then begin
                token:=Copy(o,k,i-k);
                replacetoken;
                 if (deleteprivate) then begin
                    if (token='private')or(token='public') then {now:=true}
                    else Insert(token,s,VERYBIG);
                end else Insert(token,s,VERYBIG);

            end;
            str:=true;
            Insert(o[i],s,VERYBIG);
        end else if ((o[i]='/') and (i<L) and (o[i+1]='/')) then begin
            break;

        end else if (o[i] in SEPARATORS) or ( (o[i]='.') and ((i=L) or not(o[i+1] in NUMBERS) ) and ((i=1) or nonNumeralPrefix(o,i-1) )  ) then begin
            if (k<>i) then begin
                token:=Copy(o,k,i-k);
                replacetoken;
                if (deleteprivate) then begin
                    if (token='private')or(token='public') then now:=true
                    else Insert(token,s,VERYBIG);
                end else Insert(token,s,VERYBIG);
            end;

            k:=i+1;
            if(now) then begin
                i:=i+1;
                now:=false;
            end else Insert(o[i],s,VERYBIG);

        end;
        i:=i+1;
    end;
    if ((k<>i) and not(str)) then begin
        token:=Copy(o,k,i-k);
        replacetoken;
        Insert(token,s,VERYBIG);
    end;
    while (i<=L) do begin //for comments/preprocessors
        Insert(o[i],s,VERYBIG);
        i:=i+1;
    end;

end;

//==============================================================================
// only certain keywords can be used in combination with debug
//==============================================================================
var
    validDebug: array[1..17] of string = ('local',
                                         'set',
                                         'call',

                                         'if',
                                         'else',
                                         'elseif',
                                         'endif',

                                         'loop',
                                         'endloop',
                                         'exitwhen',

                                         'for',
                                         'endfor',
                                         'while',
                                         'endwhile',
                                         'break',

                                         'hook',
                                         'implement'
                                         );

//==============================================================================
//  NOT USED
//==============================================================================
procedure VerifyValidForDebug(const s:string; decl:integer);
var x:integer;
    word:string;
    i:integer;
begin
    {s's first 'word' is debug}
    if ( CompareLineWord('debug',s,x) ) then begin
        GetLineToken(s,word,x,x);
        for i := 1 to 17 do begin
            if word = validDebug[i] then begin
                exit;
            end;
        end;
        raise JasserLineException(decl, 'Incompatible statement for debug: "'+word+'"');
    end;
end;                 

//==============================================================================
procedure parse();
var
 i,j,k,r,wordend,updatetime,updateinterval,scopelevelid,scopeid:integer;

 globalsdecl,anchor,structdecl:integer;
 period,nextperiod,injectpos:integer;
 word{,line},libpreffix_private,libpreffix_public,tmp,scope,declscopename,injectmain,postinjectmain,injectconfig:string;
 once,skip,ininjectmain,ininjectpostmain,usedmaininject,usedconfiginject,inconfiginject,skipfunction,validatedscopeinit:boolean;
 hash:TStringHash;
 scopehash:TStringHash;
 linehash:TStringHash;

 currenthash:TStringHash;

 noreplacetokens,tolibrary,toglobals,add,post,inmain,privateused,onscope,ispublic{, commusedwas //! used?}:boolean;

 scopeinitializer:string;


 stack:TScopeStack;

 ExternalBlockStdin, ExternalBlockName, ExternalBlockArgs, ExternalBlockExtension:string;
 ExternalBlockLn: integer;

 ctemp:string;

     procedure newscope(const b:boolean);
     begin
          scopeid:=i;
          if (not validScopeName(scope)) then raise JASSerLineException(i,'Invalid scope name : '+scope);
          declscopename:=scope;
          if (onscope) then begin
               stack.top(word,tmp,j,tmp,tmp);
               scope:=word+'_'+scope;
          end;
          j:=scopehash.ValueOf(scope);
          if(j>0) then begin
              raise JASSerLineDoubleException(i,'Scope redeclared : '#39+scope+#39,j,'`---- previously declared here');
          end else begin
              scopehash.Add(scope,i);
          end;


          scopelevelid:=scopelevelid+2;
          libpreffix_private:=scope+GetPrivatePrefix();
          libpreffix_public:=scope+'_';
          stack.push(scope,declscopename,scopeid,libpreffix_private,libpreffix_public);
          onscope:=true;
          if(b) then JHInput.Rewrite(k, '//! scope '+scope);
     end;
     procedure outscope;
     begin
         if(stack.empty) then begin
             raise JasserLineException(i,'Unexpected endscope/endlibrary');
         end;
         stack.pop;
         if (stack.empty) then begin
             onscope:=false;
             privateused:=false;
             scopelevelid:=0;
         end else begin
            stack.top(scope,declscopename,scopeid,libpreffix_private,libpreffix_public);
            scopelevelid:=scopelevelid-2;
         end;
     end;

     procedure addScopeInit(const name:string; const initFunc: string);
     begin
         if (length(ScopeInit)<=ScopeInitN) then begin
             SetLength(ScopeInit, 1+ScopeInitN*2);
             SetLength(ScopeName, 1+ScopeInitN*2);
         end;
         ScopeInit[ScopeInitN]:=initFunc;
         ScopeName[ScopeInitN] := name;
         ScopeInitN:=ScopeInitN+1;
     end;

     procedure addpublicforparents;
     var h:integer;
     var xword:string;
     begin
          xword:=word;
          h:=scopelevelid-2;
          while(h>0) do begin
              xword:=stack.GetScopeDecl( h div 2 )+'_'+xword;
              currenthash.Add(xword,stack.GetScopeId( (h-1) div 2));
              linehash.Add(xword,i);
              hash.Add(xword,h);
              h:=h-2;
          end;

     end;

     procedure addscopeword({wordisscope:boolean=false});
     var
        tmpi:integer;
     begin
          if(not validIdentifierName(word) ) then begin
              raise JASSERLineException(i,'Not a valid identifier name: "'+word+'"');
          end;
          tmpi:=hash.valueof(word);
          if (tmpi>0) and (tmpi<=scopelevelid-2) then begin
              tmpi:=linehash.ValueOf(word);
              if(tmpi=-1) then raise JASSERLineException(i,'Internal error');

              raise JASSERLineDoubleException(i,'Scope Identifier: '+word+' already declared in a parent scope: '+stack.list(true) ,tmpi,'\--- (previously declared here)');
          end;
          tmpi:=currenthash.ValueOf(word);
          if (tmpi=scopeid) then begin
              tmpi:=linehash.ValueOf(word);
              if(tmpi=-1) then raise JASSERLineException(i,'Internal error');

              if(JHInput.Get(tmpi)='//!keyword-public') then begin
                  if(not ispublic) then raise JASSERLineDoubleException(i,'Unable to change access flag for: '+word,tmpi,'\--- (previously declared here, as public)');

              end else if(JHInput.Get(tmpi)='//!keyword-private') then begin
                  if(ispublic) then raise JASSERLineDoubleException(i,'Unable to change access flag for: '+word,tmpi,'\--- (previously declared here, as private)');
              end
              else raise JASSERLineDoubleException(i,'Scope symbol redeclared: '+word,tmpi,'\--- (previously declared here)');
          end;

          currenthash.Add(word,scopeid);
          if(ispublic) then begin
              hash.Add(word,scopelevelid);
              addpublicforparents;
              linehash.Add(word,i);
          end else begin
              hash.Add(word,scopelevelid-1);
              linehash.Add(word,i);
          end;
     end;

     procedure beginLibrary;
     begin
                        privateused:=false;
                        add:=false;
                        if (tolibrary) then raise JASSerLineDoubleException(i,'Library nesting is not allowed.',libraries[libraryn-1].declaration,'Previous library declared here.');
                        if (onscope) then raise
                            JASSerLineDoubleException(i,'declaration of libraries inside of scope blocks not allowed',stack.bottomline,'`---- (open scopes: '+stack.list+')');

                        GetLineWord(JHInput.Get(k),word,wordend,wordend);
                        //We got the name?
                        if (word='') then raise JASSerLineException(i,'Found Unnamed library');
                        if (not validScopeName(word)) then raise JASSerLineException(i,'Invalid library name : '+word);
                        skip:=false;
                        // prevent redeclaration.
                        j:=0;
                        while(j<libraryn) do begin
                          if (libraries[j].name=word) then begin
                            if(once) then begin
                               skip:=true;
                               break;
                            end else begin
                                raise JASSerLineException(i,'library redeclared : '+word);
                            end;
                          end;
                          j:=j+1
                        end;

                        if(skip) then begin

                            anchor:=i;
                            JHInput.Rewrite(anchor, '// redeclaration of library '+word+' skipped');
                            i:=i+1;
                            while(i<=inputN) do begin
                                k:=i;
                                ctemp := JHInput.Get(k);
                                GetRealLine(i, ctemp);
                                JHInput.Rewrite(k, ctemp);
                                GetLineWord(JHInput.Get(k),word,wordend);

                                if(word='//!') then GetLineWord(JHInput.Get(k),word,wordend,wordend);
                                if(word='endlibrary') then break
                                else if (word='library') then
                                        raise JASSerLineDoubleException(i,'Library nesting is not allowed.',anchor,'Previous library declared here.');

                                i:=i+1;
                            end;
                            if(i>inputN) then raise JASSerLineException(anchor,'Missing endlibrary');
                            JHinput.Rewrite(k, JHinput.Get(anchor));
                            add:=true;
                        end else begin
                            tolibrary:=true;
                            j:=libraryn;
                            libraryn:=libraryn+1;
                            SetLength(libraries,libraryn);

                            libraries[j]:=TLibrary.Create;

                            libraries[j].contents:='';

                            //Parse the library and requirements
                            ctemp := JHInput.Get(k);
                            parseLibrary(ctemp,libraries[j],wordend,i,word);
                            JHInput.Rewrite(k, ctemp);
                            SWriteLn(libraries[j].globals,'constant boolean LIBRARY_'+libraries[j].name+'=true');
                            scope:=libraries[j].name;
                            newscope(false);
                        end;
     end;

     procedure endLibrary;
     begin
                        add:=false; //It is the end of a library!
                        if (not tolibrary) then raise JASSerLineException(i,'Unexpected endlibrary.');

                        if (not libraries[libraryn-1].validatedInit) then begin
                            if(libraries[libraryn-1].init='onInit') then begin
                                libraries[libraryn-1].init:='';
                                libraries[libraryn-1].validatedInit:=true;
                            end else
                                 raise JASSerLineException(libraries[libraryn-1].declaration,'Unable to find prototype: function '+libraries[libraryn-1].init+' takes nothing returns *** inside the library.');
                        end;
                        tolibrary:=false;
                        outscope;
     end;

     // 'InitTig'
     procedure checkForInitTrigPrefix(word:string);
     begin
        if (onscope) and hasInitTrigPrefix(word) then begin
            raise JASSERLineException(i,
                scope+' : '+word+CRLF
                +'InitTrig functions are forbidden inside libraries and scopes.'
                +CRLF+'Change function name and use initializer command.'
            );
        end;
     end;

begin
    //A line can go to 3 places: globals section, a library or main area.
    //Need to take care of : comments, strings/rawcodes (so we ignore stuff inside them
    // * Library preprocessor
    // * globals keyword
    hash:=TstringHash.Create();
    scopehash:=Tstringhash.Create();
    currenthash:=Tstringhash.Create();
    linehash:=Tstringhash.create();

    ScopeInitN:=0;
    scopeinitializer:='';
    validatedscopeinit:=false;

        injectpos:=-1;
        globals:='';
        toglobals:=false;
        globalsdecl:=-1;
        scope:='';
        declscopename:='';
        onscope:=false;
        injectconfig:='';
        injectmain:='';
        ininjectmain:=false;
        ininjectpostmain:=false;
        postinjectmain:='';
    premainarea:='';
    usedmaininject:=false;
    usedconfiginject:=false;
    inconfiginject:=false;
    skipfunction:=false;
    postmainarea:='';
    libpreffix_private:='';
    libpreffix_public:='';
    libraryn:=0;
    library_usedinit:=false;
    tolibrary:=false;
    updatetime:=0;
    stack:=TScopeStack.create;
    scopelevelid:=0;
    scopeid:=0;


    i:=0;
    post:=false;
    updateinterval:=inputN div UPDATEVALUE;
    inmain:=false;
    ResetRequireCount();
    scope:='';
    privateused:=false;
//    commused:=false;

    structdecl:=-1;

    //ProStatus('  Libraries Parsing: ');

    ExternalUsage.ResetExternalUsage();
    InitializerFunctions.Clear();

try
 try
    while (i<inputN) do begin
        //ProPosition(i/inputN);
            if (updatetime=updateinterval) then begin

               updatetime:=0;
            end else updatetime:=updatetime+1;

        add:=true;
        //line:=JHInput.Get(i);
        k:=i;

        if (JHInput.Get(k)='') then
        else begin
            //Make sure to handle strings correctly and the possible final comment
            ctemp := JHInput.Get(k);
            GetRealLine(i,ctemp);
            JHInput.Rewrite(k, ctemp);
            GetLineWord(JHInput.Get(k),word,wordend);
            if ({privateused and} ((word<>'private') and (word<>'public')) or (structdecl<>-1)  ) then begin
                ctemp := JHInput.Get(k);
                ReplaceTokens(hash,currenthash,stack,ctemp,false);
                JHInput.Rewrite(k, ctemp);
                GetLineWord(JHInput.Get(k),word,wordend);
            end;

            if(skipfunction) then begin
                add:=(word='endfunction');
                if(add) then skipfunction:=false;

            end else if (word<>'//')  then begin //comment
                if (word='//!') then begin //preprocessor
                    j:=wordend;
                    GetLineWord(JHInput.Get(k),word,wordend,j); //get a new word.
                    if (word='external') then begin

                        GetLineWord(JHInput.Get(k),word,wordend,wordend);
                        tmp:=Copy(JHInput.Get(k),wordend,Length(JHInput.Get(k)));

                        if( Length(tmp)>EXTERNAL_SIZE_LIMIT) then raise JasserLineException(i,'The external call is too long');
                        ExternalUsage.Add(word,tmp,i);
                        JHInput.Prepend('//processed : ', k);

                    end else if(word='externalblock') then begin

                        GetLineWord(JHInput.Get(k),word,wordend,wordend);
                        ExternalBlockExtension:='tmp';
                        for j := 1 to Length(word) do
                           if(word[j]='=') then begin
                               if( Copy(word,1,j-1) <> 'extension' ) then
                                   raise JasserLineException(i,'Expected: extension (before =) ');
                               ExternalBlockExtension:=Copy(word,j+1, Length(word) );
                               GetLineWord(JHInput.Get(k),word,wordend,wordend);
                               break;
                           end;


                        tmp:=Copy(JHInput.Get(k),wordend,Length(JHInput.Get(k)));

                        if( Length(tmp)>EXTERNAL_SIZE_LIMIT) then raise JasserLineException(i,'The external call is too long');
                        JHInput.Prepend('//processed : ', k);

                        ExternalBlockStdin := '';



                        ExternalBlockName:=word;

                        ExternalBlockArgs:=tmp;
                        ExternalBlockLn:=i;

                    end else if(word='i') and (ExternalBlockName <> '') then begin
                        SWriteLn(ExternalBlockStdin,Copy(JHInput.Get(k), wordend+1, Length(JHInput.Get(k)) ) );

                    end else if(word='endexternalblock') then begin
                        ExternalUsage.Add(ExternalBlockName, ExternalBlockArgs, ExternalBlockLn, ExternalBlockExtension, ExternalBlockStdIn);
                        ExternalBLockName:='';
                    end else if (word='require') then begin
                         IncRequireCount();
                    end else if (word='inject') then begin
                        if(injectpos<>-1) then begin
                            raise JASSerLineDoubleException(i,'Nested inject statement',injectpos,'Previous inject was here');
                        end;
                        injectpos:=i;
                        add:=false;
                        GetLineWord(JHInput.Get(k),word,wordend,wordend);

                        if(word='main') then begin
                            usedmaininject:=true;
                            ininjectmain:=true;
                        end else if(word='config') then begin
                             usedconfiginject:=true;
                             inconfiginject:=true;
                        end else begin
                             raise JASSerLineException(i,'Unrecognized inject option');
                        end;
                    end else if (word='endinject') then begin
                        if( ininjectmain ) then begin
                            raise JASSerLineException(i,'A well placed //! dovjassinit is required');
                        end else if(inconfiginject) then begin
                            inconfiginject:=false;
                            add:=false;
                            injectpos:=-1;
                        end else if not( ininjectpostmain) then begin
                            raise JASSerLineException(i,'Unexpected endinject.');
                        end else begin
                            ininjectpostmain:=false;
                            add:=false;
                            injectpos:=-1;
                        end;
                    end else if (word='initstructs') then begin
                        raise JASSerLineException(i,'Illegal preprocessor (used for internal operations only)');

                    end else if(word='dovjassinit') then begin

                        if(not ininjectmain) then begin
                            raise JASSerLineException(i,'//! dovjassinit must be inside //! inject main block');
                        end;
                        add:=false;
                        ininjectmain:=false;
                        ininjectpostmain:=true;

                    end else if(word='library') or (word='library_once') or (word='scope') or (word='endscope') or (word='endlibrary') then begin
                        raise JasserLineException(i,'//! syntax is deprecated for "'+word+'" please remove the //!');


                    end;  //Else it is an unknown preprocessor better keep it.


                end else if (word='struct') or (word='interface') or (word='module') then begin
                    if(structdecl<>-1) then raise JASSerLineDoubleException(i,'Nested struct',structdecl,'First struct declaration');
                    structdecl:=i;
                end else if (word='endstruct') or (word='endinterface') or (word='endmodule') then begin
                    if(structdecl=-1) then raise JASSerLineException(i,'Unexpected "'+word+'"');
                    structdecl:=-1;
                end else if ((word='library') or(word='library_once')) then begin
                    if(structdecl<>-1) then raise JASSerLineDoubleException(i,'Library inside struct?',structdecl,'struct declaration');
                    //commused:=false;
                    once:=(word='library_once');
                    beginLibrary;

                end else if (word='endlibrary') then     begin
                    endLibrary;

                end else if (word='scope') then begin
                        validatedscopeinit:=false;
                        if(onscope and (scopeinitializer<>'') ) then begin
                            raise JasserLineException(i,'Scopes with initializers cannot allow nested scopes');
                        end;

                        if(structdecl<>-1) then raise JASSerLineDoubleException(i,'Scope inside struct?',structdecl,'struct declaration');
                        //commused:=false;
                        //if (tolibrary) then raise JaSSerLineException(i,'scope declarations inside libraries not allowed');
                        GetLineWord(JHInput.Get(k),scope,j,wordend);
                        if (scope='') then JASSerLineException(i,'missing scope name');
                        if (not validScopeName(scope)) then raise JASSerLineException(i,'Invalid scope name : '+scope);

                        scopeinitializer:='';
                        r:=j;
                        if(CompareLineWord('initializer',JHInput.Get(k),r,r) ) then begin
                            if(onscope) then begin
                                raise JasserLineException(i,'Nested scopes cannot have initializers');
                            end;

                            GetLineWord(JHInput.Get(k),scopeinitializer,r,r);
                            if(not validIdentifierName(scopeinitializer)) then raise JasserLineException(i,'Invalid initializer name : '+scopeinitializer);
                            //addScopeInit(scope+'_'+scopeinitializer);
                            j:=r;
                        end else validatedscopeinit:=true;



                        VerifyEndOfLine(JHInput.Get(k),j,i);
                        newscope(true);

                end else if (word='endscope') then begin

                        if ((tolibrary) and (stack.size()=1)) or (not onscope) then raise JASSerLineException(i,'endscope found but no scope block was started');
                        if (not onscope) then raise JASSerLineException(i,'endscope found but no scope block was started');

                        if (not validatedscopeinit) then begin
                            if(scopeinitializer = 'onInit') then begin
                                scopeinitializer:='';
                            end else begin
                                raise JasserLineException(i,'Unable to find initializer: '+scopeinitializer);
                            end;
                        end;

                        //raise JASSerLineException(i,'endscope found but no scope block was started');
                        VerifyEndOfLine(JHInput.Get(k),wordend,i);
                        //JHInput.Get(k):='// scope '+scope+' ends';
                        JHInput.Rewrite(k, '//! endscope');
                        outscope;
                        scopeinitializer:='';


                end else if (word='globals') then begin //It is a globals section
                    if(structdecl<>-1) then raise JASSerLineDoubleException(i,'Globals inside struct?',structdecl,'struct declaration');
                    if (toglobals) then raise JASSerLineDoubleException(i,'nested globals',globalsdecl,'`---- first globals declaration here.');

                    VerifyEndOfLine(JHInput.Get(k),wordend,i);
                    globalsdecl:=i;
                    add:=false;

                    toglobals:=true;
                end else if  (word='endglobals') then begin //It is the end of a globals section
                    add:=false;
                    if (not toglobals) then raise JASSerLineException(i,'endglobals found but globals were not started');
                    toglobals:=false;
                    globalsdecl:=-1;
                    VerifyEndOfLine(JHInput.Get(k),wordend,i);

                end else if (word='globals') then begin
                    if(globalsdecl<>-1) then raise JASSerLineDoubleException(i,'nested globals',globalsdecl,'`---- first globals declaration here.');
                    VerifyEndOfLine(JHInput.Get(k),wordend,i);
                    globalsdecl:=i
                end else if (word='endglobals') then begin
                    if(globalsdecl=-1) then raise JASSerLineException(i,'Unexpected "endglobals"');
                    globalsdecl:=-1;
                    VerifyEndOfLine(JHInput.Get(k),wordend,i);



                end else if (word='function') then begin //It a function declaration
                    if(structdecl<>-1) then raise JASSerLineDoubleException(i,'Function inside struct?',structdecl,'struct declaration');

                //function main takes nothing returns nothing, returns nothing is probably not forcefully necessary although
                //a main function with a return value would be kind of useless I guess
                    j:=wordend;
                    GetLineWord(JHInput.Get(k),word,j,j);
                    if(word='interface') then GetLineWord(JHInput.Get(k),word,j,j);
                    checkForInitTrigPrefix(word);
                    if tolibrary then begin
                        if (not libraries[libraryn-1].validatedInit) then begin
                            if (word=libraries[libraryn-1].init) then begin
                                 GetLineWord(JHInput.Get(k),word,j,j);
                                 if (word='takes') then begin
                                     GetLineWord(JHInput.Get(k),word,j,j);
                                     if (word='nothing') then libraries[libraryn-1].validatedInit:=true;
                                 end;
                            end;


                        end;
                    end
                    else if (onscope and not(validatedscopeinit) and (word=scopeinitializer) ) then begin
                         if(compareLineWord('takes',JHInput.Get(k),j,j) and compareLineWord('nothing',JHInput.Get(k),j,j)) then begin
                              validatedscopeinit:=true;
                              addScopeInit(scope, scopeinitializer);
                         end;
                    end else if (word='main') then begin
                        GetLineWord(JHInput.Get(k),word,j,j);
                        if (word='takes') then begin
                            GetLineWord(JHInput.Get(k),word,j,j);
                            if (word='nothing') then inmain:=true;
                        end;
                        if(usedmaininject) then begin
                            SWriteLn(premainarea,JHInput.Get(k));
                            SWriteLn(premainarea,injectmain);
                            add:=false;
                        end;
                    end else if (word='config') then begin
                        GetLineWord(JHInput.Get(k),word,j,j);
                        if (word='takes') then begin
                            GetLineWord(JHInput.Get(k),word,j,j);
                            if (word='nothing') then inmain:=true;
                        end;
                        if(inmain and usedconfiginject) then begin
                            if(post) then begin
                                SWriteLn(postmainarea,JHInput.Get(k)+' // by inject');
                                SWriteLn(postmainarea,injectconfig);
                                //SWriteLn(postmainarea,'endfunction //by inject');
                            end else begin
                                SWriteLn(premainarea,JHInput.Get(k)+' // by inject');
                                SWriteLn(premainarea,injectconfig);
                                //SWriteLn(premainarea,'endfunction //by inject');

                            end;
                            add:=false;
                            skipfunction:=true;

                        end;
                        inmain:=false;
                    end;

                end else if((word='private')or(word='public')) and(structdecl=-1) then begin

                    noreplacetokens:=false;
                    ispublic:=(word='public');

                    if ((not tolibrary) and (not onscope)) then begin
                        if(ispublic) then
                            raise JasserLineException(i,'public outside library/scope definition')
                        else
                            raise JasserLineException(i,'private outside library/scope definition');
                    end;
                    if (not privateused) then begin
                        hash.Clear;
                        privateused:=true;
                    end;
                    j:=wordend;
                    GetLineToken(JHInput.Get(k),word,j,j);

                    if (word='constant') then GetLineWord(JHInput.Get(k),word,j,j);
                    if(word='struct') then structdecl:=i;
                    if(word='interface') then structdecl:=i;
                    if(word='module') then structdecl:=i;

                    if(word='library') then raise JasserLineException(i,'No private/public/ nested libraries are allowed');



                    if (word='function') then begin
                        GetLineWord(JHInput.Get(k),word,j,j);

                        if(word='interface') then GetLineWord(JHInput.Get(k),word,j,j);
                        //now word has the function name!
                        checkForInitTrigPrefix(word);
                        AddScopeWord;

                        //It should be invalid if initializer is not in the exact same library instead of a child scope
                        if (tolibrary and (scopelevelid=2) and (not libraries[libraryn-1].validatedInit) and (word=libraries[libraryn-1].init) ) then begin
                            //clever, the guy used a private initializer!
                            //word;
                            GetLineWord(JHInput.Get(k),tmp,j,j);
                            if (tmp='takes') then begin
                                GetLineWord(JHInput.Get(k),tmp,j,j);
                                if (tmp='nothing') then begin
                                     libraries[libraryn-1].validatedInit:=true;
                                     if (ispublic) then
                                         libraries[libraryn-1].Init:=libpreffix_public+word
                                     else
                                         libraries[libraryn-1].Init:=libpreffix_private+word;
                                end;
                            end;


                        end else if ( not(validatedscopeinit) and (word=scopeinitializer)) then begin
                            GetLineWord(JHInput.Get(k),tmp,j,j);
                            if (tmp='takes') then begin
                                GetLineWord(JHInput.Get(k),tmp,j,j);
                                if (tmp='nothing') then begin
                                     validatedscopeinit:=true;
                                     if (ispublic) then
                                         addScopeInit(scope, libpreffix_public+scopeinitializer)
                                     else
                                         addScopeInit(scope, libpreffix_private+scopeinitializer);
                                end;
                            end;
                        end;


                    end else if (word='scope') then  begin
                        if(structdecl<>-1) then raise JASSerLineDoubleException(i,'Scope inside struct?',structdecl,'struct declaration');
                        GetLineWord(JHInput.Get(k),scope,j,j);
                        if (scope='') then JASSerLineException(i,'missing scope name');
                        if (not validScopeName(scope)) then raise JASSerLineException(i,'Invalid scope name : '+scope);
                        VerifyEndOfLine(JHInput.Get(k),j,i);
                        if(ispublic) then begin
                            //same as a normal scope
                            newscope(true);
                        end else begin
                            //AddScopeWord; also the same...
                            newscope(true);
                        end;

                        noreplacetokens:=true;

                    end else if (word='keyword') then begin
                        GetLineToken(JHInput.Get(k),word,j,j);
                        AddScopeWord;
                        if(ispublic) then
                            JHInput.Rewrite(k, '//!keyword-public') //leave signal
                        else
                            JHInput.Rewrite(k, '//!keyword-private'); //leave signal;

                        add:=false;
                    end else if(globalsdecl=-1) and (word<>'struct') and (word<>'type') and (word<>'interface') and (word<>'module') then begin
                         raise JasserLineException(i,'Expected: "type", "struct", "interface", "function", "keyword" or "scope"');

                    end else begin
                        //word should now have forcefully a type name in word

                        //use tokens because there might be name=something
                        GetLineToken(JHInput.Get(k),word,j,j);
                        if (word='array') then GetLineToken(JHInput.Get(k),word,j,j);
                        //now we have a variable name
                        AddScopeWord;

                    end;
                    if(not noreplacetokens) then begin
                        ctemp := JHInput.Get(k);
                        ReplaceTokens(hash,currenthash,stack,ctemp,true);
                        JHInput.Rewrite(k, ctemp);
                    end;


                end else begin

                    if (inmain) then begin
                        if(usedmaininject) then begin
                            if (word='endfunction') then begin
                                SWriteLn(postmainarea,postinjectmain);
                                SWriteLn(postmainarea,'endfunction //injected main function (! inject command)??');
                                post:=true;
                                inmain:=false;
                                usedmaininject:=false;//11111111111111
                            end;
                            add:=false;
                        end else if (word='call') then begin
                            j:=wordend;
                            GetLineToken(JHInput.Get(k),word,j,j);
                            if (word='InitBlizzard') then begin
                                Insert(JHInput.Get(k),premainarea,VERYBIG); //append to premainarea
                                Insert(#13#10,premainarea,VERYBIG); //append to premainarea
                                post:=true;
                                inmain:=false;
                                add:=false;
                            end;
                        end else if (word='endfunction') then begin
                                post:=true;
                                inmain:=false;
                                add:=true;
                            //    raise JASSerLineException(i,'Missing InitBlizzard in main function');
                        end;
                    end;
                end;

            end;

        end;

        if (word='debug') then begin
            //VerifyValidForDebug(JHInput.Get(k),k);

            if (DEBUG_MODE()) then begin
                word:='';
                for j := 1 to wordend-6 do word:=word+' ';
                JHInput.Rewrite(k, word+Copy(JHInput.Get(k),wordend,Length(JHInput.Get(k))-wordend+1));
            end else begin
                JHInput.Prepend('// NO_DEBUG: ', k);
                //add:=false;
            end;
        end;


        if add then begin
            if (inconfiginject) then begin
                SWriteLn(injectconfig,JHInput.Get(k));

            end else if ininjectpostmain then begin
                SWriteLn(postinjectmain,JHInput.Get(k));
            end else if ininjectmain then begin
                SWriteLn(injectmain,JHInput.Get(k));
            end else if toglobals then begin
                if(tolibrary) then begin
                    SWriteLn(libraries[libraryn-1].globals,JHInput.Get(k));
                end else begin
                    Insert(JHInput.Get(k),globals,VERYBIG); //append to globals section
                    Insert(#13#10,globals,VERYBIG);
                end;
            end else if tolibrary then begin
                Insert(JHInput.Get(k),libraries[libraryn-1].contents,VERYBIG); //append to library
                Insert(#13#10,libraries[libraryn-1].contents,VERYBIG);
            end else if (post) then begin
                Insert(JHInput.Get(k),postmainarea,VERYBIG); //append to premainarea
                Insert(#13#10,postmainarea,VERYBIG);
            end else begin
                Insert(JHInput.Get(k),premainarea,VERYBIG); //append to postmainarea
                Insert(#13#10,premainarea,VERYBIG);
            end;
        end;

        i:=i+1;
    end;
    if(structdecl<>-1) then raise JASSerLineException(structdecl,'Missing endstruct');
    if (toglobals) then raise JASSerLineException(globalsdecl,'Missing endglobals');
    if (globalsdecl<>-1) then raise JASSerLineException(globalsdecl,'Missing endglobals');


    if (injectpos<>-1) then raise JASSerLineException(injectpos,'Missing endinjenct');
    if (tolibrary) then raise JASSerLineException(libraries[libraryn-1].declaration,'Missing endlibrary');
    if (onscope) then begin
         raise JASSerLineException(stack.bottomline,'Missing endscope');
    end;
    if (postmainarea='') then  begin
        if (inmain) then raise JASSerLineException(1,'Could not find call to InitBlizzard in main function!')
        else raise JASSerLineException(1,'Could not find correct main function in file. / unclosed string');
    end;

    for i := 0 to libraryn - 1 do begin
        j:= 0;
        while( j<libraries[i].reqn ) do begin
            {if (scopehash.ValueOf(libraries[i].req[j])<0) then begin
                 raise JASSerLineException(libraries[i].declaration,'Missing requirement: '+libraries[i].req[j]);
            end; This actually increased time}
            k:=0;
            while(k<libraryn) do begin
                if(libraries[k].name=libraries[i].req[j]) then break;
                k:=k+1;
            end;
            if(k=libraryn) then begin
                if (libraries[i].req_opt[j]) then begin
                    libraries[i].reqn := libraries[i].reqn - 1;
                    libraries[i].req[j]:= libraries[i].req[ libraries[i].reqn ];
                    libraries[i].req_opt[j]:= libraries[i].req_opt[ libraries[i].reqn ];
                    j:=j-1;
                end else begin
                    raise JASSerLineException(libraries[i].declaration,'Missing requirement: '+libraries[i].req[j]+' (libraries cannot require scopes)' );
                end;
            end;
            j:=j+1;
        end;
    end;

    //ProEnd();


    except
       on e:EAccessviolation do begin
           raise JasserLineDoubleException(i,'[Internal Error]',i,e.message);
       end;
    end;
finally
    hash.Destroy;
    stack.Destroy;
    scopehash.Destroy;
    currenthash.Destroy;
    linehash.Destroy;

end;


end;

//==============================================================================
//  Writes the jasshelper code in proper library order into string Result.
//==============================================================================
procedure writeInOrder(var Result:string);
var
   i:integer;
   initializers:string;
   librarycode:string;
begin
    Result:='';

    removedLibrariesN:=0;
    SetLength(removedLibraries,5);

    SWriteLn(Result,'globals');

    initializers:='';
    librarycode:='';
    FindLibraryCycles;  // if cycles throw exception

    while (libraryn>0) do begin
        i:=findNoReqLibrary;   // find the next library to write
        if (i=-1) then begin
            cleanAddedLibraries;   // clean up requirements
            i:=findNoReqLibrary;   // try again
            if (i=-1) then raise JASSerLineException(libraries[0].declaration,'Library requirements cycle, involved libraries are: '+getInvolvedLibraries);
        end;

        // write the current library into librarycode section
        // save library boundaries inside special //! comments
        SWriteLn(librarycode,'//! library '+libraries[i].name);
        SWriteLn(librarycode,libraries[i].contents);
        SWriteLn(librarycode,'//! endlibrary '+libraries[i].name);

        // write globals at the start of output
        if(libraries[i].globals<>'') then begin
            SWriteLn(Result,'//globals from '+libraries[i].name+':');
            Swrite(Result,libraries[i].globals);
            SWriteLn(Result,'//endglobals from '+libraries[i].name);
        end;

        // save library init function names, forwarded to structs stage.
        InitializerFunctions.AddLibraryInit(libraries[i].name, libraries[i].Init);
        {
        // write out library initializers into initializers section
        if (libraries[i].Init<>'') then
            SWriteLn(initializers,'call ExecuteFunc("'+libraries[i].Init+'")');
        }

        removeLibrary(i);

    end;                   

    SWriteLn(Result,globals);

    // TODO: Remove next line, it is old dead code (breaks diff tests)
    //if(library_usedinit) then
    //    SWriteLn(Result,'trigger l__library_init');

    SWriteLn(Result,'endglobals');

    // write the libraries into result
    Swrite(Result,librarycode);

    {
    // append scope initializers to the global initializer section
    for i := 0 to ScopeInitN - 1 do begin
        Insert('call '+ScopeInit[i]+'()'#13#10,initializers,VERYBIG);
    end;
    }
    
    // add scope initializers to the list for later generation
    for i := 0 to ScopeInitN - 1 do begin
        InitializerFunctions.AddLibraryInit(scopeName[i], ScopeInit[i]);
    end;

    // inject the initializers section in the map's main function
    SWriteLn(Result,premainarea);
    SWriteLn(Result,'//! initstructs');   // marker
    SWriteLn(Result,initializers);
    //It is better to have them after the library initializers. Why?
    SWriteLn(Result,'//! initdatastructs');  // marker
    SWriteLn(Result, postmainarea);
end;


//==============================================================================
//  END
//==============================================================================
end.


