
    <<CompilerServerCommandImpl.mesa>>
        <<Copyright  1985 by Xerox Corporation.  All rights reserved.>>
        <<Satterthwaite, June 5, 1986 12:01:30 pm PDT>>
        <<Maxwell, January 26, 1984 9:05:33 am PST>>
        <<Paul Rovner, December 4, 1983 11:57 am>>
        <<Spreitzer, May 16, 1984 7:45:09 pm PDT>>
        <<Russ Atkinson (RRA) April 3, 1985 4:02:43 pm PST>>
        <<Bob Hagmann March 24, 1986 10:39:12 am PST>>

    DIRECTORY
        Ascii: TYPE USING [Lower],
        BasicTime: TYPE USING [GMT, Now, Period, ToNSTime],
        ComputeServerServer: TYPE USING [Register],
        Commander: TYPE USING [Handle],
        CommandUtil: TYPE USING [PairList, Echo, Failed, GetNth, GetRootName, ListLength, Parse, SetExtension],
        CompilerOps: TYPE USING [AppendHerald, DefaultSwitches, DoTransaction, LetterSwitches, Punt, Start, Stop, StreamId, Transaction],
        FileNames: TYPE USING [CurrentWorkingDirectory, GetShortName],
        FileParms: TYPE USING [BindingProc, nullActual],
        FileParmOps: TYPE USING [ClearAList, Finalize, Initialize, SetAList],
        FS: TYPE USING [Delete, Error, FileInfo, GetName, nullOpenFile, OpenFile, OpenFileFromStream, StreamOpen],
        IO: TYPE USING [Close, EndOfStream, Error, GetTokenRope, IDProc, int, Put, PutChar, PutRope, RIS, rope, RopeFromROS, ROS, STREAM, 
        time],
        Process: TYPE USING [CheckForAbort, GetPriority, MsecToTicks, Priority, priorityBackground, SetPriority, SetTimeout],
        Rope: TYPE USING [Cat, Fetch, Length, ROPE, Substr],
        SymbolTable: TYPE USING [CacheSize, SetCacheSize],
        TimeStamp: TYPE USING [Null];

    CompilerServerCommandImpl: MONITOR
        IMPORTS Ascii, BasicTime, CommandUtil, CompilerOps, ComputeServerServer, FileNames, FileParmOps, FS, IO, Process, Rope, SymbolTable
        = { 

    Outcome: TYPE = {ok, warnings, errors, aborted};

    parms: REF CompilerOps.Transaction = NEW[CompilerOps.Transaction];
    standardDefaults: CompilerOps.LetterSwitches = CompilerOps.DefaultSwitches[];
    sourceName, objectName, errorName, rootName, wDir: Rope.ROPE _ NIL;
    sourceStream, objectStream, errorStream: IO.STREAM _ NIL;
    <<>>
    useLog: BOOL;        -- use compiler.log for error reporting
    log: IO.STREAM _ NIL;

    compilerInUse: BOOL _ FALSE;
    inUseChanged: CONDITION;
    inUseTimeoutMillis: NAT _ 1000;

    WaitForCompilerFree: ENTRY PROC RETURNS[BOOL] = {
        ENABLE UNWIND => NULL;
        IF compilerInUse THEN {
            Process.CheckForAbort[];
            Process.SetTimeout[@inUseChanged, Process.MsecToTicks[inUseTimeoutMillis]];
            WAIT inUseChanged;
            RETURN[FALSE]};
        RETURN[compilerInUse _ TRUE]};

    SetCompilerFree: ENTRY PROC = {
        compilerInUse _ FALSE;
        BROADCAST inUseChanged};

    Compile: SAFE PROC[cmd: Commander.Handle] RETURNS[result: REF _ NIL, msg: Rope.ROPE _ NIL] ~ TRUSTED { 
        userAbort: BOOL _ FALSE;    -- set by ^DEL, STOP
        errors, warnings: BOOL _ FALSE;
        priority: Process.Priority = Process.GetPriority[];
        compilerStartTime, moduleStartTime: BasicTime.GMT;
        switchDefaults: CompilerOps.LetterSwitches;
        moduleCount: CARDINAL _ 0;
        complex: BOOL = (SELECT cmd.procData.clientData FROM
        $Vanilla => FALSE, $Complex => TRUE, $RemoteVanilla => FALSE, ENDCASE => ERROR);

        cmdStream: IO.STREAM;
        filesInit: BOOL _ FALSE;
        started: BOOL _ FALSE;

        Cleanup: PROC = {
            <<assumes that msg has a one character code in it (on entry) for the success/falilure of the compilation>>
            Process.SetPriority[priority];
            IF log # NIL THEN {
                msg _ msg.Cat[" - ", log.RopeFromROS];
                log.Close[ ! IO.Error => CONTINUE];
                log _ NIL};
            IF started THEN {started _ FALSE; CompilerOps.Stop[]};
            IF filesInit THEN {filesInit _ FALSE; FileParmOps.Finalize[]};
            SetCompilerFree[]};

        Initialize: PROC  = {
            sourceStream _ FS.StreamOpen[sourceName];
            parms.sourceStream _ sourceStream;
            parms.source.version _ TimeStamp.Null;
            parms.source.version.time _ (FS.FileInfo[name: sourceName, wDir: wDir].created).ToNSTime};

        Finalize: PROC[userAbort: BOOL] = {
            sourceFileName: Rope.ROPE;
            IF objectStream # NIL THEN objectStream.Close[ ! IO.Error => CONTINUE];
            IF sourceStream # NIL THEN {
                sourceFile: FS.OpenFile _ FS.OpenFileFromStream[sourceStream];
                sourceFileName _ sourceFile.GetName[ ! FS.Error => CONTINUE].fullFName;
                sourceStream.Close[ ! IO.Error => CONTINUE]};
            IF errorStream # NIL AND errorStream # log THEN
                errorStream.Close[ ! IO.Error => CONTINUE];
            objectStream _ sourceStream _ errorStream _ NIL;
            IF userAbort OR parms.nErrors # 0 THEN {
                IF objectName # NIL THEN
                    DO
                        FS.Delete[name: objectName, wDir: wDir 
                            ! FS.Error => IF error.group = lock OR error.code = $unknownFile THEN EXIT];
                        ENDLOOP;
                };
            IF errorName = NIL THEN {
                errlogFileName: Rope.ROPE _ CommandUtil.SetExtension[rootName, "errlog"]; 
                DO
                    FS.Delete[name: errlogFileName, wDir: wDir 
                        ! FS.Error => IF error.group = lock OR error.code = $unknownFile THEN EXIT];
                    ENDLOOP;
                };
            };

        <<acquire the compiler's resource lock; await success>>
        IF NOT WaitForCompilerFree[] THEN RETURN[$Failure, "B"];  -- Blocked

        <<init cmdStream and sourceName>>

        cmdStream _ IO.RIS[cmd.commandLine];
        sourceName _ NIL;

        <<do the compilation>>
        Process.SetPriority[Process.priorityBackground];
        msg _ "A"; -- in case of UNWIND, claim we were aborted
        BEGIN ENABLE UNWIND => Cleanup[];

            StartPass: PROC[pass: CARDINAL] RETURNS[goOn: BOOL] = {
                userAbort _ FALSE;
                Process.CheckForAbort[! ABORTED => {userAbort _ TRUE; CONTINUE}];
                cmd.out.PutRope[" ." ! ABORTED => {userAbort _ TRUE; CONTINUE}];
                IF userAbort THEN cmd.out.PutRope[" aborted."];
                RETURN[~userAbort]};

            compilerStartTime _ BasicTime.Now[];
            switchDefaults _ CompilerOps.DefaultSwitches[];
            parms.fileParms _ FileParmOps.Initialize[];
            filesInit _ TRUE;
            CompilerOps.Start[];
            started _ TRUE;

            wDir _ FileNames.CurrentWorkingDirectory[];
            <<IF log = NIL THEN log _ FS.StreamOpen[fileName: "Compiler.log", accessOptions: $create];>>
            IF log = NIL THEN log _ IO.ROS[];
            WriteHerald[log, NIL];  

            DO
                first: BOOL;
                args, results: CommandUtil.PairList;
                switches: Rope.ROPE _ NIL;
                localPause: BOOL;
                sense: BOOL;

                {  -- start scope for EXITS
                    parms.switches _ switchDefaults;
                    parms.switches['p] _ FALSE;
                    parms.debugPass _ CARDINAL.LAST;
                    parms.getStream _ GetStream;
                    parms.startPass _ StartPass;
                    parms.objectBytes _ parms.objectFrameSize _ parms.linkCount _ 0;
                    parms.nErrors _ parms.nWarnings _ 0;
                    parms.sourceTokens _ 0;

                    IF complex THEN
                        [sourceName, args, results, switches] _ CommandUtil.Parse[cmdStream
                            ! CommandUtil.Failed => GOTO badSyntax]
                    ELSE {
                        <<use a simple syntax for the command line>>
                        token: Rope.ROPE;
                        token _ cmdStream.GetTokenRope[IO.IDProc ! IO.EndOfStream => EXIT].token;
                        IF token.Length[] > 0 THEN SELECT token.Fetch[0] FROM
                            '- => switches _ token.Substr[1, token.Length[]-1];
                            ENDCASE => sourceName _ token};
                    IF sourceName = NIL AND switches = NIL THEN EXIT;
                    log.PutRope["\nCommand: "];
                    CommandUtil.Echo[log, sourceName, args, results, switches];
                    IF CommandUtil.ListLength[results] > 1 THEN GOTO badSemantics;
                    IF sourceName = NIL THEN GOTO globalSwitches;
                    rootName _ (CommandUtil.GetRootName[IF CommandUtil.ListLength[results] = 1
                        THEN CommandUtil.GetNth[results, 0]
                        ELSE FileNames.GetShortName[sourceName]]);
                    IF switches # NIL THEN {
                        sense _ TRUE;
                        FOR i: INT IN [0..switches.Length[]) DO
                            c: CHAR = switches.Fetch[i];
                            SELECT c FROM
                                '-, '~ => {sense _ ~sense; LOOP};
                                IN ['a..'z], IN ['A..'Z] =>
                                    parms.switches[Ascii.Lower[c]] _ sense;
                                IN ['1..'5] =>
                                    parms.debugPass _ c-'0;
                                ENDCASE;
                            sense _ TRUE;
                            ENDLOOP;
                        switches _ NIL};
                    sourceName _ CommandUtil.SetExtension[sourceName, "mesa"];
                    parms.source.locator _ FileNames.GetShortName[sourceName];

                    IF CommandUtil.ListLength[results] # 0 THEN {
                        objectName _ CommandUtil.GetNth[list: results, n: 0, delete: TRUE];
                        results _ NIL}
                    ELSE objectName _ rootName;
                    objectName _ CommandUtil.SetExtension[objectName, "bcd"];
                    parms.objectName _ objectName;
                    parms.objectFile _ FS.nullOpenFile;

                    moduleCount _ moduleCount + 1;

                    <<feedback to user>>
                    cmd.out.Put[IO.rope["Compiling: "], IO.rope[rootName]];
                    first _ TRUE;
                    FOR c: CHAR IN ['a..'z] DO
                        sd: BOOL = (IF c = 'p THEN FALSE ELSE standardDefaults[c]);
                        IF parms.switches[c] # sd THEN {
                            IF first THEN {first _ FALSE; cmd.out.PutChar['/]};
                            IF sd THEN cmd.out.PutChar['-];
                            cmd.out.PutChar[c]};
                        ENDLOOP;
                    useLog _ parms.switches['g];  parms.switches['g] _ FALSE;
                    localPause _ parms.switches['p];  parms.switches['p] _ FALSE;

                    <<main line code>>
                    Initialize[ ! FS.Error => GOTO noSource];
                    sourceName _ FileNames.GetShortName[sourceName];
                    { ENABLE UNWIND => Finalize[userAbort];
                        FileParmOps.SetAList[args];
                        { ENABLE UNWIND => FileParmOps.ClearAList[];

                            BindPattern: FileParms.BindingProc = {
                                parms.pattern _ actual;
                                parms.op _ IF actual = FileParms.nullActual THEN $compile ELSE $replace};

                            parms.fileParms.Binding[formalId: "$", formalType: NIL, binder: BindPattern];

                            log.PutChar['\n];  
                            moduleStartTime _ BasicTime.Now[];
                            CompilerOps.DoTransaction[parms
                                ! 
                                    CompilerOps.Punt => {
                                        FileParmOps.ClearAList[];
                                        Finalize[userAbort];
                                        GOTO punt};
                                    ABORTED => {
                                        userAbort _ TRUE;
                                        FileParmOps.ClearAList[];
                                        Finalize[userAbort];
                                        GOTO truncateList};
                                    ];
                            };  -- end  ENABLE UNWIND => FileParmOps.ClearAList[];
                        FileParmOps.ClearAList[];
                        };  -- end ENABLE UNWIND => Finalize[];
                    Finalize[userAbort];

                    SELECT WriteResults[cmd.out, moduleStartTime ! IO.Error => CONTINUE] FROM
                        $errors => errors _ TRUE;
                        $warnings => warnings _ TRUE;
                        ENDCASE;

                    EXITS
                        globalSwitches => {
                            objectName _ NIL;
                            sense _ TRUE;
                            FOR i: INT IN [0..switches.Length[]) DO
                                c: CHAR = switches.Fetch[i];
                                SELECT c FROM
                                    '-, '~ => sense _ ~sense;
                                    IN ['a..'z] => {switchDefaults[c] _ sense; sense _ TRUE};
                                    IN ['A..'Z] => {
                                        switchDefaults[VAL['a.ORD+(c.ORD-'A.ORD)]] _ sense; sense _ TRUE};
                                    ENDCASE => EXIT;
                                ENDLOOP;
                            switches _ NIL;
                            args _ NIL};
                        noSource => {
                            log.Put[IO.rope[" -- source not found\n"], IO.time[]];
                            cmd.out.PutRope[" -- source not found\n"];
                            errors _ TRUE;
                            parms.nErrors _ 1;
                            args _ NIL};
                        badSemantics => {
                            objectName _ NIL; errors _ TRUE;
                            log.PutRope[" -- Illegal command"];
                            args _ NIL};
                    };

                <<Here after completion of subcommand>>
                sourceName _ rootName _ objectName _ errorName _ NIL;
                parms.objectName _ NIL;
                results _ NIL;
                log.PutChar['\n];
                IF userAbort THEN {log.PutRope["\n... command aborted\n"];  GOTO truncateList};
                IF (errors OR warnings) AND localPause THEN GOTO truncateList;

                REPEAT
                    badSyntax => {log.PutRope["\n-- Illegal syntax"]; errors _ TRUE};
                    truncateList => switchDefaults['p] _ TRUE;
                    punt => {-- was Finalize[]; , but this is done by new UNWIND catch phrase
                        errors _ TRUE;
                        [] _ WriteResults[cmd.out, moduleStartTime ! IO.Error => CONTINUE];
                        log.PutChar['\n]};
                ENDLOOP;
            <<Here after completion of all subcommands>>
            WriteClosing[cmd.out, compilerStartTime, moduleCount];

        SELECT TRUE FROM 
        userAbort => {
        result _ $Failure;
        msg _ "A"};
        errors => {
        result _ $Failure;
        msg _ "F"};
        warnings => {
        result _ $Failure;
        msg _ "W"};
        ENDCASE =>  {
        result _ $Success;
        msg _ "S"};
            END;  -- end catch phrase to release the resource and reset the process priority
        Cleanup[]};
    <<>>
    <<>>
    <<compiler sequencing>>

    WriteResults: PROC[out: IO.STREAM, startTime: BasicTime.GMT] RETURNS[Outcome] = {
        elapsed: INT;
        log.Put[IO.rope[sourceName], IO.rope[" -- "]];
        elapsed _ startTime.Period[to: BasicTime.Now[]];
        IF parms.nErrors # 0 THEN {
            log.Put[IO.rope["aborted, "], IO.int[parms.nErrors], IO.rope[" errors"]];
            IF parms.nWarnings # 0 THEN {
                log.Put[IO.rope[" and "], IO.int[parms.nWarnings], IO.rope[" warnings"]]};
            IF ~useLog THEN log.Put[IO.rope[" on "], IO.rope[wDir], IO.rope[errorName]];
            log.Put[IO.rope[", seconds: "], IO.int[elapsed]]}
        ELSE {
            log.Put[IO.rope["source tokens: "], IO.int[parms.sourceTokens]];
            log.Put[IO.rope[", seconds: "], IO.int[elapsed]];
            IF parms.objectBytes # 0 THEN {
                log.Put[IO.rope["\n  code bytes: "], IO.int[parms.objectBytes]]; 
                log.Put[IO.rope[", links: "], IO.int[parms.linkCount]];
                log.Put[IO.rope[", frame size: "], IO.int[parms.objectFrameSize]];
                IF parms.matched THEN log.PutChar['.]};
            IF parms.nWarnings # 0 THEN {
                log.PutChar['\n];
                log.Put[IO.int[parms.nWarnings], IO.rope[" warnings"]];
                IF log # NIL AND ~useLog THEN log.Put[IO.rope[" on "], IO.rope[errorName]]}};
        <<write to the Commander output file>>
        out.PutRope[" "];
        IF parms.nErrors = 0 THEN out.PutRope["no errors"]
        ELSE out.Put[IO.int[parms.nErrors], IO.rope[" errors"]];
        IF parms.nWarnings # 0 THEN
            out.Put[IO.rope[", "], IO.int[parms.nWarnings], IO.rope[" warnings"]];
        out.PutRope[".\n"];
        RETURN[SELECT TRUE FROM
            parms.nErrors # 0 => $errors,
            parms.nWarnings # 0 => $warnings,
            ENDCASE => $ok];
        };

    WriteHerald: PROC[s: IO.STREAM, id: Rope.ROPE] = {
        CompilerOps.AppendHerald[s];
        s.PutRope[" (Cedar 6 Version)\n"];
        IF id # NIL THEN {s.Put[IO.rope[id], IO.rope[" -- "]]};
        s.Put[IO.time[], IO.rope["\n"]]};

    WriteClosing: PROC[out: IO.STREAM, startTime: BasicTime.GMT, moduleCount: CARDINAL] = {
        elapsed: INT;
        out.PutRope["End of compilation\n"];
        elapsed _ startTime.Period[to: BasicTime.Now[]];
        IF moduleCount > 1 THEN log.Put[IO.rope["\nTotal elapsed seconds: "], IO.int[elapsed]];
        log.PutChar['\n]};

    <<special output stream control>>

    GetStream: PROC[id: CompilerOps.StreamId] RETURNS[IO.STREAM] = {
        SELECT id FROM
            source => RETURN[sourceStream];
            object => {
                IF objectStream = NIL THEN objectStream _ NewOutputStream[objectName];
                RETURN[objectStream]};
            log => {IF errorStream = NIL THEN ErrorInit[]; RETURN[errorStream]};
            ENDCASE => ERROR;
        };

    NewOutputStream: PROC[fileName: Rope.ROPE] RETURNS[IO.STREAM] = {
        RETURN[FS.StreamOpen[fileName: fileName, accessOptions: $create]]}; 

    ErrorInit: PROC = {
        IF errorStream = NIL THEN
            IF useLog THEN errorStream _ log
            ELSE {
                errorName _ CommandUtil.SetExtension[rootName, "errlog"];
                errorStream _ FS.StreamOpen[fileName: errorName, accessOptions: $create];
                WriteHerald[errorStream, errorName];
                errorStream.PutChar['\n]
            }
        };

    <<START HERE>>
    IF SymbolTable.CacheSize[] < 256 THEN SymbolTable.SetCacheSize[256];
    ComputeServerServer.Register["RemoteCompiler", NIL, Compile, "", $Vanilla];
    ComputeServerServer.Register["RemoteComplexCompiler", NIL, Compile, "", $Complex];


    }.

    <<Bob Hagmann March 24, 1986 8:41:20 am PST>>
        <<changed log writing from writing Compiler.log to writing a ROS and returning it as part of the parameter "msg">>
        <<changes to: Cleanup, Compile>>
    <<>>
    <<>>
