//==============================================================================
//  This unit contains string manipulation functions
//  that are used by multiple jasshelper stages.
//==============================================================================
unit TextCommon;

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

function CompareSubString(const s:string; st:integer; en:integer; const val:string):boolean;
function CompareLineWord(const match:string; const  line:string; var endIndex:integer; startIndex:integer=1):boolean;
function CompareLineToken(const match:string; const  line:string; var endIndex:integer; startIndex:integer=1):boolean;
procedure GetLineWord(const  line:string; var word:string ; var endIndex:integer; startIndex:integer=1);
procedure GetLineToken(const line: string; var token:string ; out endIndex:integer; startIndex:integer=1);
function TestGetLineWord(line:string;i:integer):string;
procedure GetLineWordAlsoComma(line:string; var st:string ; var endIndex:integer; startIndex:integer=1);

function GetEndOfUsefulLine(const s:string):integer;
procedure SkipString(const s:string; var a:integer; const b:integer);
procedure SkipUselessBrackets(const s:string; var a:integer; var b:integer);
function validScopeName(const s:string):boolean;
function validIdentifierName(const s:string):boolean;

function hasInitTrigPrefix(const functionName:string):boolean;
function extractTriggerName(const line:string):string;
function lineFromTESH(const line:string):boolean;

//==============================================================================
//  IMPLEMENTATION
//==============================================================================
implementation
uses TextOnly, Constants;

//==============================================================================
function CompareSubString(const s:string; st:integer; en:integer; const val:string):boolean;
var
  L1,L2,j:integer;
begin
    L1:=Length(s);
    L2:=Length(val);

    if (en-st+1<>L2) then begin
        Result:=false;
        Exit;
    end;
    if ((en>L1) or (st<1)) then begin
         Result:=false;
         exit;
    end;
    j:=1;
    while (st<=en) do begin
        if (s[st]<>val[j]) then begin
            Result:=false;
            Exit;
        end;
        j:=j+1;
        st:=st+1;
    end;
 Result:=true;
end;


//==============================================================================
function CompareLineWord(const match:string; const  line:string; var endIndex:integer; startIndex:integer=1):boolean;
var
 i,L:integer;
begin

   L:=Length(line);
   if (L=0) then begin
       Result:=(match='');
       exit;
   end;
   i:=startIndex;
   //st:='';
   // because // is accepted after keywords it also handles //
   while (i<=L) do begin
      if (line[i] in [' ',#9]) then i:=i+1 //#9 is tab
      else break;
   end;
   endIndex:=i+1;
   while (endIndex<=L) do begin
        if ((line[endIndex]='/') and (endIndex<L) and (line[endIndex+1]='/')) then begin
            //st:=Copy(line,i,endIndex-1);
            break;
        end;

        if (line[endIndex] in [' ',#9]) then break;
        endIndex:=endIndex+1;
   end;
   Result:=CompareSubString(line,i,endIndex-1,match);
end;

//==============================================================================
function CompareLineToken(const match:string; const  line:string; var endIndex:integer; startIndex:integer=1):boolean;
var
 i,L:integer;
begin

   L:=Length(line);
   if (L=0) then begin
       endIndex:=1;
       Result:=(match='');
       exit;
   end;
   i:=startIndex;
   //st:='';
   // because // is accepted after keywords it also handles //
   while (i<=L) do begin
      if (line[i] in SEPARATORS) then i:=i+1 //#9 is tab
      else break;
   end;
   endIndex:=i+1;
   while (endIndex<=L) do begin
        if (line[endIndex] in SEPARATORS) then break;
        endIndex:=endIndex+1;
   end;
   Result:=CompareSubString(line,i,endIndex-1,match);
end;

//==============================================================================
//  Gets first word in a string starting from index startIndex.
//  Properly handles single line comments //
//  Examples:
//     aaa bbb ccc -> aaa
//     aaa/// bbb ccc -> aaa
//     ///aaa bbb ccc -> ///aaa
//
//  first argument: line string
//  second argument: out word
//  third argument: end position at the end of returned word in input line
//  fourth argument: start position
//==============================================================================
procedure GetLineWord(const  line:string; var word:string ; var endIndex:integer; startIndex:integer=1);
var
 i,L:integer;
 commprotect:boolean;
begin
   L:=Length(line);
   // if empty string return null results
   if (L=0) then begin
       endIndex:=0;
       word:='';
       exit;
   end;
   // start from specified index
   i:=startIndex;
   //st:='';

   // skip starting whitespaces
   while (i<=L) do begin
      if (line[i] in [' ',TAB]) then i:=i+1
      else break;
   end;

   // protects from comments that are stuck to a keyword
   // example:  endlibrary//================
   commprotect := ((i>L) or (line[i]<>'/'));
   endIndex:=i+1;
   while (endIndex<=L) do begin
        if ( commprotect and (line[endIndex]='/') and (endIndex<L) and (line[endIndex+1]='/')) then begin
            //st:=Copy(line,i,endIndex-1);
            break;
        end;
        if (line[endIndex] in [' ',TAB]) then break;
        endIndex:=endIndex+1;
   end;
   word:=Copy(line,i,endIndex-i);
end;

//==============================================================================
//  ignores jass strings and start of comment // but gets tokens in comment too
//==============================================================================
procedure GetLineToken(const line: string; var token:string ; out endIndex:integer; startIndex:integer=1);
var
 i,L:integer;

begin

   L:=Length(line);
   if (L=0) then begin
       endIndex:=0;
       token:='';
       exit;
   end;
   i:=startIndex;
   //st:='';

   // because // is accepted after keywords it also handles //
   while (i<=L) do begin
      if( line[i] = '"') then begin
          i:=i+1;
          while(i<=L) do begin
              if(line[i]='"') then break;
              if(line[i]='\') then i:=i+1;
              i:=i+1;
          end;
          i:=i+1;
      end else if( line[i] = '''') then begin
          i:=i+1;
          while(i<=L) and (line[i]<>'''') do
              i:=i+1;
          i:=i+1;

      end else if line[i] in SEPARATORS then i:=i+1
      else if
      (line[i]='.')
      and
      ((i=1)
          or nonNumeralPrefix(line,i-1))
       and ( (i=L)
          or not(line[i+1] in NUMBERS) ) then
          i:=i+1
       //support for struct syntax.
      else break;
   end;
   endIndex:=i+1;
   while (endIndex<=L) do begin
        if line[endIndex] in SEPARATORS then break;
        endIndex:=endIndex+1;
   end;
   token:=Copy(line,i,endIndex-i);
end;

//==============================================================================
function TestGetLineWord(line:string;i:integer):string;
var j:integer;
begin
    GetLineWord(line,Result,j,i);
end;


//==============================================================================
procedure GetLineWordAlsoComma(line:string; var st:string ; var endIndex:integer; startIndex:integer=1);
var
 i,L:integer;
begin

   L:=Length(line);
   if (L=0) then begin
       endIndex:=0;
       st:='';
       exit;
   end;
   i:=startIndex;
   //st:='';
   while (i<=L) do begin
      if (line[i] in [' ',#9,',']) then i:=i+1 //#9 is tab
      else break;
   end;
   endIndex:=i+1;
   while (endIndex<=L) do begin
        if (line[endIndex] in [' ',#9,',']) then break;
        endIndex:=endIndex+1;
   end;
   st:=Copy(line,i,endIndex-i);
end;

//==============================================================================
function GetEndOfUsefulLine(const s:string):integer;
var i,L,j:integer;
ctr:boolean;
begin
    L:=Length(s);
    if(IsWhiteSpace(s) or (L=0) ) then begin
         result:=1;
         exit;
    end;
    i:=1;

    while (i<=L) do begin
        if (s[i]=' ') or (s[i]=#9) then begin
            j:=i+1;
            while(j<=L) and ((s[j]=' ')or(s[j]=#9)) do j:=j+1;
            if (j>L) or ((j+1<=L) and (s[j]='/') and (s[j+1]='/')) then begin
                result:=i;
                exit;
            end;
            i:=j;
        end else if(i<=L+1) and (s[i]='/') and (s[i+1]='/') then begin
            result:=i;
            exit;
        end else if(s[i]='''') then begin
            if(i+2<=l) and (s[i+2]='''') then i:=i+3
            else i:=i+6;
        end else if (s[i]='"') then begin
            ctr:=false;
            i:=i+1;
            while(i<=L)do begin
                if(ctr) then ctr:=false
                else if(s[i]='\') then ctr:=true
                else if(s[i]='"') then break;
                i:=i+1;

            end;
            i:=i+1;
        end else i:=i+1;
    end;
    result:=L+1;

end;

//==============================================================================
// s[a] is the beginning of a string, moves a to the end of such string or b,
// whatever comes first.
//==============================================================================
procedure SkipString(const s:string; var a:integer; const b:integer);
var ctr:boolean;
begin
    if(s[a]='''') then begin
        if(a+2<=b) and (s[a+2]='''') then a:=a+2
        else if(a+5<=b) then a:=a+5
        else a:=b;
        exit;
    end;

    if(s[a]='"') then begin
        ctr:=false;
        while(a<b) do begin
            if(ctr) then ctr:=false
            else if(s[a]='\') then ctr:=true
            else if(s[a]='"') then break;
            a:=a+1;
        end;


    end;
end;

//==============================================================================
procedure SkipUselessBrackets(const s:string; var a:integer; var b:integer);
var i,par,ra,rb:integer;


begin
    ra:=a;
    rb:=b;
    while(true) do begin
        while( (ra<rb) and ((s[ra]=' ')or (s[ra]=#9) ) ) do ra:=ra+1;
        if(ra=rb) then break; //why give this function a bunch of whitespace?
        while( (rb>ra) and ((s[rb]=' ')or (s[rb]=#9) ) ) do rb:=rb-1;
        if(s[rb]<>')') or(s[ra]<>'(') then break;
        //are these useful?
        i:=ra+1;
        par:=0;
        while(i<=rb-1) and (par>=0) do begin
            if(s[i]='(') or (s[i]='[') then  par:=par+1
            else if(s[i]=')') or (s[i]=']') then par:=par-1
            else if(s[i]='''') or (s[i]='"') then SkipString(s,i,rb-1);

            i:=i+1;

        end;
        if(par<>0) then break; //not useless / error
        ra:=ra+1;
        rb:=rb-1;

    end;
    a:=ra;
    b:=rb;
end;









//==============================================================================
function validScopeName(const s:string):boolean;
var
   i,L:integer;
begin
    i:=1;
    L:=Length(s);
    Result:=true;
    while(i<=L) do begin
        if ((s[i]>='a') and (s[i]<='z')) or ((s[i]>='A') and (s[i]<='Z') or ((s[i] in ['0'..'9'])and(i>1)) ) then
            i:=i+1
        else begin
            Result:=false;
            break;
        end;
    end;

end;

//==============================================================================
function validIdentifierName(const s:string):boolean;
var
   i,L:integer;
begin
    i:=1;
    L:=Length(s);
    Result:=true;
    while(i<=L) do begin
        if ((s[i]>='a') and (s[i]<='z')) or ((s[i]>='A') and (s[i]<='Z')) or ( (((s[i]>='0') and (s[i]<='9')) or (s[i]='_')) and(i>1)) then
            i:=i+1
        else begin
            Result:=false;
            break;
        end;
    end;

end;


//==============================================================================
const InitTrig  = 'InitTrig';
const InitTrig_ = 'InitTrig_';
//==============================================================================
function hasInitTrigPrefix(const functionName:string):boolean;
var i:integer;
begin
    if Length(functionName)<8 then begin
        Result := false;
        exit;
    end;

    if Length(functionName)=8 then begin
        Result := (functionName=InitTrig);
        exit;
    end;

    Result := true;
    for i := 1 to 9 do begin
        if InitTrig_[i] <> functionName[i] then begin
            Result := false;
            exit;
        end;
    end;
end;

//==============================================================================
const TrigPrefix  = '// Trigger: ';
//==============================================================================
function extractTriggerName(const line:string):string;
begin
    if Length(line) <= Length(TrigPrefix) then begin
        Result := '';
        exit;
    end;
    if Copy(line, 1, Length(TrigPrefix)) = TrigPrefix then begin
        Result := Copy(line, Length(TrigPrefix)+1, Length(line)-Length(TrigPrefix));
    end else begin
        Result := '';
    end;
end;

//==============================================================================
const TESHPrefix = '//TESH.';
//==============================================================================
function lineFromTESH(const line:string):boolean;
begin
    Result := false;
    if Length(line) >= Length(TESHPrefix) then begin
        if Copy(line, 1, Length(TESHPrefix)) = TESHPrefix then begin
            Result := true;
        end;
    end;
end;

end.
