
    <<TestInterface.mesa>>
        <<Copyright © 1985 by Xerox Corporation.  All rights reserved.>>
        <<Satterthwaite, December 19, 1985 9:42:44 am PST>>
        <<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 May 24, 1985 11:48:36 am PDT>>

    DIRECTORY
        Ascii: TYPE USING [Lower],
        BasicTime: TYPE USING [GMT, Now, Period, ToNSTime],
        Commander: TYPE USING [CommandObject, Handle, Register],
        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, STREAM, time],
        Process: TYPE USING [CheckForAbort, GetPriority, MsecToTicks, Priority, priorityBackground, SetPriority, SetTimeout],
        Rope: TYPE USING [Fetch, Length, ROPE, Substr],
        TimeStamp: TYPE USING [Null];

    CrossInterface: MONITOR
        IMPORTS Ascii, BasicTime, Commander, CommandUtil, CompilerOps, FileNames, FileParmOps, FS, IO, Process, Rope
        = { 

    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 = {
            Process.SetPriority[priority];
            IF log # NIL THEN {IO.Close[log ! 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 _
            BasicTime.ToNSTime[FS.FileInfo[name: sourceName, wDir: wDir].created];
            };

        Finalize: PROC[userAbort: BOOL] = {
            sourceFileName: Rope.ROPE;
            IF objectStream # NIL THEN IO.Close[objectStream ! IO.Error => CONTINUE];
            IF sourceStream # NIL THEN {
                sourceFile: FS.OpenFile _ FS.OpenFileFromStream[sourceStream];
                sourceFileName _ FS.GetName[sourceFile ! FS.Error => CONTINUE].fullFName;
                IO.Close[sourceStream ! IO.Error => CONTINUE]};
            IF errorStream # NIL AND errorStream # log 
                THEN IO.Close[errorStream ! 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];
        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];
            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;
                    IO.PutRope[log, "\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[]];
                            IO.PutRope[cmd.out, " -- 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 => {IO.PutRope[log, "\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: Outcome] = {
        OPEN IO;
        elapsed: INT;
        log.Put[rope[sourceName], rope[" -- "]];
        elapsed _ BasicTime.Period[from: startTime, to: BasicTime.Now[]];
        IF parms.nErrors # 0 THEN {
            log.Put[rope["aborted, "], int[parms.nErrors], rope[" errors"]];
            IF parms.nWarnings # 0 THEN {
                log.Put[rope[" and "], int[parms.nWarnings], rope[" warnings"]]};
            IF ~useLog THEN log.Put[IO.rope[" on "], IO.rope[wDir], IO.rope[errorName]];
            log.Put[rope[", seconds: "], int[elapsed]]}
        ELSE {
            log.Put[rope["source tokens: "], int[parms.sourceTokens]];
            log.Put[rope[", seconds: "], int[elapsed]];
            IF parms.objectBytes # 0 THEN {
                log.Put[rope["\n  code bytes: "], int[parms.objectBytes]]; 
                log.Put[rope[", links: "], int[parms.linkCount]];
                log.Put[rope[", frame size: "], int[parms.objectFrameSize]];
                IF parms.matched THEN PutChar[log, '.]};
            IF parms.nWarnings # 0 THEN {
                log.PutChar['\n];
                log.Put[int[parms.nWarnings], 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];
        IO.PutRope[s, " (Cedar 5 Version)\n"];
        IF id # NIL THEN {IO.Put[s, IO.rope[id], IO.rope[" -- "]]};
        IO.Put[s, IO.time[], IO.rope["\n"]];
        };

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

    <<special output stream control>>

    GetStream: PROC[id: CompilerOps.StreamId] RETURNS[s: 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[stream: IO.STREAM] = {
        stream _ 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>>
    Commander.Register["CrossCompile", Compile, "", $Complex];

    }.
