//==============================================================================
//  Unit that stores and orders initializer functions
//  for libraries, structs and modules.
//==============================================================================
unit InitializerFunctions;

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

procedure Clear;
procedure AddLibraryInit(const libraryName:string; const initFuncName:string);
procedure AddStructInit(const libraryName:string; const structName:string; const initFuncName:string);
procedure AddModuleInit(const libraryName:string; const structName:string; const moduleCalls:string);

function GetExecuteList():string;

//==============================================================================
//  IMPLEMENTATION
//==============================================================================
implementation
uses SysUtils, StringHash, Constants, TextCommon, TextOnly;

//==============================================================================
//  Data structure for ordering initializers. (slanted binary tree)
//==============================================================================
type
    PNode = ^TNode;
    TNode = record
        data:string;
        next:PNode;
        firstChild:PNode;
    end;

var
    N : integer;

    FirstNode :PNode;
    LastNode :PNode;

    // array for accessing PNode by index
    AllNodes : array of PNode;

    // hashes for saving (name -> PNode index) pairs
    libraryHash : TStringHash;
    structHash  : TStringHash;


//==============================================================================
//  Save the node into array and return index for hashing.
//==============================================================================
function LogPNode(const p:PNode):integer;
begin
    // array resizing here?
    Result := N;
    AllNodes[N]:= p;
    N := N + 1;
end;

//==============================================================================
function NewNode(const initFuncName:string):PNode;
begin
    new(Result);
    Result.data := initFuncName;
    Result.next := nil;
    Result.firstChild := nil;
end;

//==============================================================================
//  Adds library to the arrays even if it has no init function.
//  We will use it as a position marker for structs even if empty.
//  We assume libraries are added here in descending priority order.
//
//  We assume scopes are added in the normal parsing order.
//  Scopes are just bottom libraries, so we can add them with the same function.
//==============================================================================
procedure AddLibraryInit(const libraryName:string; const initFuncName:string);
var
    p : PNode;
    index : integer;
begin
    p := NewNode(initFuncName);
    index := LogPNode(p);
    libraryHash.Add(libraryName, index);
    LastNode.next := p;
    LastNode := p;
end;

//==============================================================================
procedure AddChildNode(const parent:PNode; const child:PNode);
var lastChild : PNode;
begin
    if parent.firstChild = nil then begin
        // node is a first child of this parent
        parent.firstChild := child;
    end else begin
        // node is not a first child, add it to the end
        lastChild := parent.firstChild;
        while lastChild.next <> nil do begin
            lastChild := lastChild.next;
        end;
        lastChild.next := child;
    end
end;

//==============================================================================
//  This is called after preprocessor stage.
//  So we assume libraries are already ordered before this is called.
//==============================================================================
procedure AddStructInit(const libraryName:string; const structName:string; const initFuncName:string);
var
    p : PNode;
    index : integer;
    parentIndex : integer;
begin
    p := NewNode(initFuncName);
    index := LogPNode(p);
    structHash.Add(libraryName+'$'+structName, index);

    if libraryName='' then begin
        parentIndex := -1;
    end else begin
        // can return -1 for unregistered scopes
        parentIndex := libraryHash.ValueOf(libraryName);
    end;

    if parentIndex = -1 then begin
        // no parent scope, create anonymous scope and add struct to it.
        LastNode.next := NewNode('');
        LastNode := LastNode.next;
        AddChildNode(LastNode, p);
    end else begin
        // has parent scope , add to end of scopes's child list
        AddChildNode(AllNodes[parentIndex], p)
    end;
end;

//==============================================================================
procedure AddModuleInit(const libraryName:string; const structName:string; const moduleCalls:string);
var
    p : PNode;
    parentIndex : integer;
begin
    p := NewNode(moduleCalls);
    // No need to log modules they have no children

    // modules must have a parent struct
    if structName='' then begin
        raise Exception.Create('AddModuleInit : '+moduleCalls+' - module init outside of struct???');
    end else begin
        parentIndex := structHash.ValueOf(libraryName+'$'+structName);
        if parentIndex = -1 then begin
            raise Exception.Create('AddModuleInit : '+libraryName+'$'+structName+' - struct not registered???');
        end;
    end;

    // Add to end of struct's child list
    AddChildNode(AllNodes[parentIndex], p)
end;


//==============================================================================
//==============================================================================
//  OUTPUT SECTION
//==============================================================================
//==============================================================================

//==============================================================================
//  Some inits are added already inside ExecuteFunc so we must be careful
//==============================================================================
function TransformCall(const init:string):string;
var vj:integer;
begin
    Result := '';
    if init<>'' then begin
        if CompareLineWord('call', init,vj) then begin
            Result := init;
        end else begin
            Result := 'call ExecuteFunc("'+init+'")'+CRLF;
        end;                     
    end;
end;

//==============================================================================
//  Get all execute calls, starting from first node.
//==============================================================================
function GetExecuteList():string;
var
    pLib   : PNode;
    pStruct: PNode;
    pModule: PNode;
    temp   : string;
begin
    pLib := FirstNode;
    Result := CRLF;
    while pLib<>nil do begin
        temp := '';
        // struct initializers go before library init
        pStruct := pLib.firstChild;
        while pStruct<>nil do begin
            // module initializers go before struct init
            pModule := pStruct.firstChild;
            while pModule<>nil do begin
                // add module init
                temp := temp + TransformCall(pModule.data);
                // move to next module
                pModule := pModule.next;
            end;
            // add struct init
            temp := temp + TransformCall(pStruct.data);
            // move to the next struct
            pStruct := pStruct.next;
        end;
        // add library init
        temp := temp + TransformCall(pLib.data);
        // write out library block
        if temp<>'' then begin
            Result := Result
                    + temp
                    + '//------------------------------------' + CRLF;
        end;
        // move to the next library
        pLib := pLib.next;
    end;
end;

//==============================================================================
procedure Clear;
begin
    libraryHash := TStringHash.create();
    structHash  := TStringHash.create();

    N := 0;
    // I don't expect more than 20000 initializer functions :)
    SetLength(AllNodes, 20*1024);

    // first node has empty data
    new(FirstNode);
    FirstNode.data  := '';
    FirstNode.next  := nil;
    FirstNode.firstChild := nil;
    LastNode := FirstNode;
end;

//==============================================================================
//  INITIALIZATION
//==============================================================================
begin
    Clear;
end.
