-- file Interface.Mesa
-- last modified by Satterthwaite, April 13, 1981  10:45 AM
-- last modified by Sweet, March 27, 1981  8:50 AM
-- last modified by Karlton, Feb 14, 1981 11:54 AM

DIRECTORY
  AltoDisplay: TYPE USING [
    CursorBits, Coordinate, DCB, DCBHandle, DCBchainHead,
    Cursor, CursorXY, MaxBitsPerLine, MaxScanLines],
  CharIO: TYPE USING [
    CR, NumberFormat, GetChar, PutChar, PutDecimal, PutLine, PutNumber, PutString],
  CommandUtil: TYPE USING [
    PairList,
    CopyString, Echo, Failed, FreePairList, FreeString, GetNth, ListLength, Parse,
    SetExtension, SkipNonBlank],
  CompilerOps: TYPE USING [
    TableHandle, TableId, StreamId, Transaction, CompileAction, Punt, Sequencer],
  CoreSwapDefs: TYPE USING [level],
  FrameDefs: TYPE USING [IsBound],
  ImageDefs: TYPE USING [
    FileRequest, AbortMesa, AddFileRequest, ImageTime, RunImage, StopMesa],
  Inline: TYPE USING [DIVMOD],
  KeyDefs: TYPE USING [Keys, KeyBits],
  MiniDisplay: TYPE USING [Append, Clear, Initialize, Finalize],
  MiscDefs: TYPE USING [CallDebugger, CommandLineCFA],
  MiscOps: TYPE USING [AllocateDebuggerBitmap, BitmapPages, ReleaseDebuggerBitmap],
  OsStaticDefs: TYPE USING [OsStatics],
  SDDefs: TYPE USING [sAddFileRequest, SD],
  Segments: TYPE USING [
    FHandle, FP, FPHandle, SHandle, Read, Write,
    DeleteSegment, DestroyFile, EnumerateDirectory, GetFileTimes,
    InsertFile, LockFile, NewFile, NewSegment, SetFileTimes, UnlockFile],
  Storage: TYPE USING [FreePages, Pages],
  StreamDefs: TYPE USING [JumpToFA],
  Streams: TYPE USING [
    Handle, CreateStream, Destroy, Ended, FileFromStream, GetBlock, GetIndex, 
    PutChar, PutBlock, Restart, Suspend],
  String: TYPE USING [AppendChar, AppendString, EquivalentString],
  StringDefs: TYPE USING [MesaToBcplString, WordsForBcplString],
  Style: TYPE USING [ItemQueueInit],
  Time: TYPE USING [Append, AppendCurrent, Unpack];
  
Interface: PROGRAM [
    explicitSwapping: BOOLEAN,
    tableSegment: ARRAY CompilerOps.TableId [parse..debug] OF CompilerOps.TableHandle]
  IMPORTS
      CharIO, CommandUtil, CompilerOps, CoreSwapDefs, FrameDefs, ImageDefs,
      Inline, MiniDisplay, MiscDefs, MiscOps, Segments, Storage, StreamDefs,
      Streams, String, StringDefs, Style, Time
  EXPORTS CompilerOps, Style = 
  BEGIN
  
-- command input

  formatting: BOOLEAN = FrameDefs.IsBound[Style.ItemQueueInit];
  headerComment: PUBLIC STRING ← NIL;
  commandStream: Streams.Handle;
  comCmRequest: ImageDefs.FileRequest ← [
    name: "Com.Cm.", file: NIL, access: Segments.Read, link: ];
    
  SetCommandInput: PROC = INLINE {
    IF comCmRequest.file = NIL
      THEN {
	comCmRequest.file ← Segments.NewFile[comCmRequest.name, Segments.Read];
	Segments.LockFile[comCmRequest.file]};
    commandStream ← Streams.CreateStream[comCmRequest.file, Segments.Read];
    IF ~image
      THEN StreamDefs.JumpToFA[commandStream, @(MiscDefs.CommandLineCFA[]).fa]
      ELSE CommandUtil.SkipNonBlank[commandStream]};
      
      
-- command logging

  log: Streams.Handle;
  logName: STRING = IF formatting THEN "Formatter.log." ELSE "Compiler.Log.";
  logRequest: ImageDefs.FileRequest ← [
    name: logName, file: NIL, access: Segments.Write, link: ];
    
  SetTypescript: PROC = INLINE {
    IF logRequest.file = NIL
      THEN {
	logRequest.file ← Segments.NewFile[logName, Segments.Write];
	Segments.LockFile[logRequest.file]};
    log ← Streams.CreateStream[logRequest.file, Segments.Write]};
    
  NewLine: PROC = {CharIO.PutChar[log, CharIO.CR]};
  
  WriteHerald: PROC [s: Streams.Handle, id: STRING] = {
    OPEN CharIO;
    time: STRING ← [20];
    PutString[s, 
      IF formatting THEN "Mesa Formatter 7.0/6.1 of "L
      ELSE "Mesa Compiler 6.1 of "L];
    time.length ← 0;  Time.Append[time, Time.Unpack[ImageDefs.ImageTime[]]];
    PutLine[s, time];
    IF id # NIL THEN {PutString[s, id]; PutString[s, " -- "L]};
    time.length ← 0;  Time.AppendCurrent[time];
    PutLine[s, time]};
    
  WriteTime: PROC [sec: CARDINAL] = {
    OPEN CharIO;
    hr, min: CARDINAL;
    f: NumberFormat ← [base:10, unsigned:TRUE, zerofill:FALSE, columns:1];
    
    W: PROC [t: CARDINAL] = {
      IF t # 0 OR f.zerofill
	THEN {
	  PutNumber[log, t, f];  PutChar[log, ':];
	  f ← [base:10, unsigned:TRUE, zerofill:TRUE, columns:2]}};
	  
    [min, sec] ← Inline.DIVMOD[sec, 60];  [hr, min] ← Inline.DIVMOD[min, 60];
    W[hr];  W[min];  PutNumber[log, sec, f]};
    
    
-- user feedback

  fontSeg: Segments.SHandle;
  fontRequest: ImageDefs.FileRequest ← [
    name: "sysfont.al", file: NIL, access: Segments.Read, link: ];
    
    
-- compiler input and output stream management

  sourceName: STRING;
  objectName: STRING;
  rootName: STRING ← [40];
  
  sourceFile: Segments.FHandle ← NIL;
  sourceStream: Streams.Handle ← NIL;
  
  objectFile: Segments.FHandle ← NIL;
  objectStream: Streams.Handle ← NIL;
  
  scratchFile: Segments.FHandle ← NIL;
  
  ObjectInit: PROC = INLINE {
    IF objectFile = NIL
      THEN {
	objectFile ← Segments.NewFile[objectName, Segments.Write];
	Segments.LockFile[objectFile]};
    objectStream ← Streams.CreateStream[objectFile, Segments.Write]};
    
  ObjectReset: PROC [keep: BOOLEAN] = INLINE {
    IF keep
      THEN {
	IF objectFile # NIL THEN Segments.UnlockFile[objectFile];
	IF objectStream # NIL THEN Streams.Destroy[objectStream]}
      ELSE {
	IF objectStream # NIL THEN Streams.Destroy[objectStream];
	IF objectFile # NIL THEN {
	  IF formatting THEN {
	    sourceFile ← Streams.FileFromStream[sourceStream];
	    Segments.LockFile[sourceFile];
	    CopyFile[from: sourceFile, to: objectFile];
	    Segments.UnlockFile[objectFile];
	    Segments.UnlockFile[sourceFile]; sourceFile ← NIL}
	  ELSE {Segments.UnlockFile[objectFile]; Segments.DestroyFile[objectFile]}}};
      objectStream ← NIL;  objectFile ← NIL};
      
      
  errorFile: Segments.FHandle ← NIL;
  errorStream: Streams.Handle ← NIL;
  
  ErrorInit: PROC = {
    errorName: STRING ← CommandUtil.SetExtension[
	CommandUtil.CopyString[rootName, 2+("errlog"L).length],
	"errlog"L];
    IF errorFile = NIL
      THEN {
	errorFile ← Segments.NewFile[errorName, Segments.Write];
	Segments.LockFile[errorFile]};
    errorStream ← Streams.CreateStream[errorFile, Segments.Write];
    WriteHerald[errorStream, errorName];
    CharIO.PutChar[errorStream, CharIO.CR];
    errorName ← CommandUtil.FreeString[errorName]};
    
  ErrorReset: PROC = INLINE {
    IF errorFile # NIL
      THEN {
	Segments.UnlockFile[errorFile];
	SELECT TRUE FROM
	  errorStream # NIL => {errorStream.destroy[errorStream]; errorStream ← NIL};
	  errorFile # NIL => Segments.DestroyFile[errorFile];
	  ENDCASE;
	errorFile ← NIL}};
	
	
  GetStream: PROC [id: CompilerOps.StreamId] RETURNS [s: Streams.Handle] = {
    SELECT id FROM
      source => s ← sourceStream;
      object => {IF objectStream = NIL THEN ObjectInit[]; s ← objectStream};
      log => {IF errorStream = NIL THEN ErrorInit[]; s ← errorStream};
      ENDCASE => ERROR;
    RETURN};
    
    
-- compiler sequencing

  Initialize: PROC = {
    IF log # NIL THEN Streams.Suspend[log];
    IF commandStream # NIL THEN Streams.Suspend[commandStream];
    moduleStartTime ← secondsClock.low};
    
  Finalize: PROC [started: BOOLEAN] = {
    ObjectReset[keep: ~started OR parms.nErrors = 0];
    IF sourceStream # NIL
      THEN {Streams.Destroy[sourceStream]; sourceStream ← NIL};
    ErrorReset[];
    IF commandStream # NIL THEN Streams.Restart[commandStream];
    IF log # NIL THEN Streams.Restart[log]};
    
  CopyFile: PROCEDURE [from, to: Segments.FHandle] =
    BEGIN OPEN Streams;
    bufSize: CARDINAL = 20;
    buffer: POINTER TO PACKED ARRAY [0..bufSize*256*2) OF CHARACTER =
      Storage.Pages[bufSize];
    words: CARDINAL;
    end, odd: BOOLEAN ← FALSE;
    scratch, source: Streams.Handle;
    scratch ← CreateStream[to, Segments.Write];
    source ← CreateStream[from, Segments.Read];
    UNTIL end DO
      words ← GetBlock[source, buffer, bufSize*256];
      IF (end ← Ended[source]) AND GetIndex[source] MOD 2 # 0 THEN {
	odd ← TRUE; words ← words - 1}; 
      [] ← PutBlock[scratch, buffer, words];
      IF odd THEN PutChar[scratch, buffer[words*2]];
      ENDLOOP;
    Storage.FreePages[buffer];
    Segments.SetFileTimes[file: to, create: Segments.GetFileTimes[file: from].create];
    source.destroy[source];
    scratch.destroy[scratch];
    END;
    
  WriteClosing: PROC = {
    OPEN AltoDisplay, CharIO;
    IF (CursorXY.y ← CursorXY.y+16) > MaxScanLines-64 THEN CursorXY.y ← 64;
    PutString[log, sourceName];  PutString[log, " -- "L];
    IF parms.nErrors # 0
      THEN {
	CursorXY.x ← MIN[MaxBitsPerLine-64, CursorXY.x+16];
	errors ← TRUE;  PutString[log, "aborted, "L];
	PutDecimal[log, parms.nErrors];  PutString[log, " errors "L];
	IF parms.nWarnings # 0
	  THEN {
	    warnings ← TRUE;  PutString[log, "and "L];
	    PutDecimal[log, parms.nWarnings];  PutString[log, " warnings "L]};
	PutString[log, "on "L];
	PutString[log, rootName]; PutString[log, ".errlog"L]}
      ELSE {
	PutString[log, "source tokens: "L];
	PutNumber[log, parms.sourceTokens,
	    [base:10,zerofill:FALSE,unsigned:TRUE,columns:1]];
	PutString[log, ", time: "L];
	WriteTime[secondsClock.low-moduleStartTime];
	IF ~formatting AND parms.objectBytes # 0
	  THEN {
	    NewLine[]; 
	    PutString[log, "  code bytes: "L]; PutDecimal[log, parms.objectBytes];
	    PutString[log, ", links: "L]; PutDecimal[log, parms.linkCount];
	    PutString[log, ", frame size: "L];
	    PutDecimal[log, parms.objectFrameSize]};
	IF parms.nWarnings # 0
	  THEN {
	    warnings ← TRUE;  NewLine[];
	    PutDecimal[log, parms.nWarnings];  PutString[log, " warnings on "L];
	    PutString[log, rootName];  PutString[log, ".errlog"L]}}};
	    
	    
  StopCompiler: PROC [toRun: BOOLEAN ← FALSE] = {
    IF moduleCount > 1
      THEN {
	NewLine[];  CharIO.PutString[log, "Total elapsed time: "L];
	WriteTime[secondsClock.low-compilerStartTime];  NewLine[]};
    Segments.UnlockFile[logRequest.file];  logRequest.file ← NIL;
    Streams.Destroy[log];
    Segments.UnlockFile[comCmRequest.file];  comCmRequest.file ← NIL;
    Streams.Destroy[commandStream];
    MiniDisplay.Finalize[];
    IF fontSeg # NIL
      THEN {
	Segments.DeleteSegment[fontSeg];  fontSeg ← NIL;
	Segments.UnlockFile[fontRequest.file];  fontRequest.file ← NIL};
    IF SwitchDefaults['q] AND (~toRun OR errors OR warnings)
      THEN {	-- got here from Debugger
	CoreSwapDefs.level ← -1;
	MiscOps.AllocateDebuggerBitmap[MiscOps.BitmapPages];
	MiscDefs.CallDebugger[SELECT TRUE FROM 
	  errors => "Errors logged"L,
	  warnings => "Warnings logged"L,
	  ENDCASE => "Compilation complete"L]};
    IF (errors OR warnings) AND SwitchDefaults['p]
      THEN {
	BlankCursor: AltoDisplay.CursorBits = ALL[0];
	QueryCursor: AltoDisplay.CursorBits = [ 
	  2000b, 74000b, 140000b, 12767b, 12525b, 53566b, 111113b, 163100b,
	  0b, 0b, 154000b, 53520b, 62520b, 53360b, 155440b, 140b];
	  
	ReadKeys: PROC [p: POINTER TO KeyDefs.KeyBits] = {
	  p↑ ← KeyDefs.Keys↑;
	  LOOPHOLE[p, POINTER]↑ ← 0;	-- mouse, keyset
	  p.LeftShift ← p.Ctrl ← p.Spare3 ← up};
	  
	savedKeys, newKeys: KeyDefs.KeyBits;
	RTC: POINTER TO MACHINE DEPENDENT RECORD [high: [0..4096), low: [0..16)] =
	  LOOPHOLE[430B];
	savedTime: CARDINAL;
	state: {off, on1, on2};
	ReadKeys[@savedKeys];
	AltoDisplay.Cursor↑ ← BlankCursor;  state ← off;  savedTime ← RTC.high;
	  DO
	  IF RTC.high # savedTime
	    THEN {
	      SELECT state FROM
		off => {AltoDisplay.Cursor↑ ← QueryCursor; state ← on1};
		on1 => state ← on2;
		on2 => {AltoDisplay.Cursor↑ ← BlankCursor; state ← off};
		ENDCASE;
	      savedTime ← RTC.high};
	  ReadKeys[@newKeys];
	  IF newKeys # savedKeys THEN EXIT;
	  ENDLOOP}};
	  
	  
  transaction: CompilerOps.Transaction;
  parms: POINTER TO CompilerOps.Transaction = @transaction;
  
  SwitchDefaults: PACKED ARRAY CHARACTER ['a..'z] OF BOOLEAN ← InitDefaults;
  InitDefaults: PACKED ARRAY CHARACTER ['a..'z] OF BOOLEAN = [
  --a/k/u  b/l/v  c/m/w  d/n/x  e/o/y  f/p/z  g/q    h/r    i/s    j/t --
     TRUE, FALSE, FALSE, FALSE, FALSE,  TRUE, FALSE, FALSE, FALSE,  TRUE,
    FALSE, FALSE,  TRUE, FALSE, FALSE,  TRUE, FALSE, FALSE,  TRUE,  TRUE,
    FALSE, FALSE,  TRUE, FALSE, FALSE, FALSE];
    
  FindFiles: PROC [fp: Segments.FPHandle, s: STRING] RETURNS [BOOLEAN] = {
    IF s.length = sourceName.length AND String.EquivalentString[sourceName, s] THEN {
      sourceFile ← Segments.InsertFile[fp, Segments.Read];
      Segments.LockFile[sourceFile]};
    IF ~formatting AND s.length = objectName.length AND
	String.EquivalentString[objectName, s] THEN {
      objectFile ← Segments.InsertFile[fp, Segments.Write];
      Segments.LockFile[objectFile]};
    IF formatting AND formatterOverwrite AND s.length = ("Formatter.scratch$."L).length AND
	String.EquivalentString["Formatter.scratch$."L, s] THEN {
      scratchFile ← Segments.InsertFile[fp, Segments.Write]; 
      Segments.LockFile[scratchFile]};
    IF s.length = rootName.length + (".errlog."L).length THEN {
	errorName: STRING ← [40];
	String.AppendString[errorName, rootName];
	String.AppendString[errorName, ".errlog."L];
	IF String.EquivalentString[errorName, s] THEN {
	    errorFile ← Segments.InsertFile[fp, Segments.Write];
	    Segments.LockFile[errorFile]}};
    RETURN [sourceFile # NIL AND errorFile # NIL AND (formatting OR objectFile # NIL)]};
    
  errors, warnings: BOOLEAN;
  formatterOverwrite: BOOLEAN;
  moduleCount: CARDINAL;
  compilerStartTime, moduleStartTime: CARDINAL;
  secondsClock: POINTER TO MACHINE DEPENDENT RECORD [high, low: CARDINAL] =
    LOOPHOLE[572B];
    
    
  -- * * * * * *  M A I N   B O D Y   C O D E  * * * * * *
  
  image: BOOLEAN = (SDDefs.SD[SDDefs.sAddFileRequest] # 0);
  bitmapFrozen: BOOLEAN ← FALSE;
  
  dcbSpace: ARRAY [0..SIZE[AltoDisplay.DCB]+1) OF UNSPECIFIED;
  dcb, saveDCB: AltoDisplay.DCBHandle;
  
-- cursor management

  L1: WORD = 147777b;
  R1: WORD = 177763b;
  M1: WORD = 177177b;
  Two: WORD = 147763b;
  
  ClearCursor: PROC = INLINE {AltoDisplay.Cursor↑ ← ALL[177777b]};
  
  SetCursor: PROC [position: {upper, middle, lower}, row: WORD] = {
    SELECT position FROM
      upper => AltoDisplay.Cursor[2] ← AltoDisplay.Cursor[3] ← row;
      middle => AltoDisplay.Cursor[7] ← AltoDisplay.Cursor[8] ← row;
      lower => AltoDisplay.Cursor[12] ← AltoDisplay.Cursor[13] ← row;
      ENDCASE};
      
  UpdateCursor: PROC [pass: CARDINAL] RETURNS [goOn: BOOLEAN ← TRUE] = {
    ClearCursor[];
    IF errors THEN {AltoDisplay.Cursor[5] ← 125252b; AltoDisplay.Cursor[10] ← 052525b};
    SELECT pass FROM
      1 => SetCursor[middle, M1];
      2 => {SetCursor[upper, L1]; SetCursor[lower, R1]};
      3 => {SetCursor[upper, R1]; SetCursor[middle, M1]; SetCursor[lower, L1]};
      4 => {SetCursor[upper, Two]; SetCursor[lower, Two]};
      5 => {SetCursor[upper, Two]; SetCursor[middle, M1]; SetCursor[lower, Two]};
      6 => {SetCursor[upper, Two]; SetCursor[middle, Two]; SetCursor[lower, Two]}
      ENDCASE};
      
  saveCursor: AltoDisplay.CursorBits;
  saveCoordinate: AltoDisplay.Coordinate;
  
-- add cleanup procedure
  IF image
    THEN {
      ImageDefs.AddFileRequest[@comCmRequest];
      ImageDefs.AddFileRequest[@logRequest];
      ImageDefs.AddFileRequest[@fontRequest]}  -- wait for restart
    ELSE {
      fp: Segments.FP ← MiscDefs.CommandLineCFA[].fp;
      comCmRequest.file ← Segments.InsertFile[@fp, Segments.Read];
      Segments.LockFile[comCmRequest.file]};
  STOP;  -- wait for restart (noop unless image)
  
  dcb ← @dcbSpace[0];
  IF LOOPHOLE[dcb, CARDINAL] MOD 2 # 0 THEN dcb ← dcb + 1;
  dcb↑ ← AltoDisplay.DCB[NIL, high, white, 0, 0, NIL, 0];
  saveDCB ← AltoDisplay.DCBchainHead↑;  AltoDisplay.DCBchainHead↑ ← dcb;
  saveCoordinate ← AltoDisplay.CursorXY↑;  saveCursor ← AltoDisplay.Cursor↑;
  ClearCursor[];  AltoDisplay.CursorXY↑ ← [64,64];
  IF fontRequest.file = NIL
    THEN {
      fontRequest.file ← Segments.NewFile[fontRequest.name, Segments.Read];
      Segments.LockFile[fontRequest.file]};
  fontSeg ← IF fontRequest.file # NIL
	THEN Segments.NewSegment[fontRequest.file, 1, 0, Segments.Read]
	ELSE NIL;
  MiniDisplay.Initialize[fontSeg];
  
  START CompilerOps.Sequencer[explicitSwapping, tableSegment];
  
-- do the compilation

  SetCommandInput[];  SetTypescript[];
  compilerStartTime ← secondsClock.low;  moduleCount ← 0;
  WriteHerald[log, NIL];  errors ← warnings ← formatterOverwrite ← FALSE;
  
  WHILE TRUE 
    DO
    
    SetRoot: PROC [root, s: STRING] = {
      root.length ← 0;
      FOR i: CARDINAL IN [0..s.length)
	DO
	IF s[i] = '. THEN EXIT;
	String.AppendChar[root, s[i]];
	ENDLOOP};
	
    args, results: CommandUtil.PairList;
    switches: STRING;
    sense: BOOLEAN;
    
      BEGIN  OPEN CharIO;
      ClearCursor[];  MiniDisplay.Clear[];
      parms.op ← IF formatting THEN format ELSE compile;
      parms.system ← alto;
      parms.getStream ← GetStream;  parms.startPass ← UpdateCursor;
      parms.switches ← SwitchDefaults;  parms.switches['p] ← FALSE;
      parms.debugPass ← LAST[CARDINAL];
      [sourceName, args, results, switches] ←
	CommandUtil.Parse[
		s: commandStream,
		opX: 2+("mesa").length, resultX: 2+("bcd").length
	  ! CommandUtil.Failed => GO TO badSyntax];
      IF sourceName = NIL AND switches = NIL THEN EXIT;
      NewLine[];  PutString[log, "Command: "];
      CommandUtil.Echo[log, sourceName, args, results, switches];
      IF CommandUtil.ListLength[results] > 1 THEN GO TO badSemantics;
      IF sourceName = NIL THEN GO TO globalSwitches;
      SetRoot[rootName, IF CommandUtil.ListLength[results] = 1
	THEN CommandUtil.GetNth[results, 0] ELSE sourceName];
      IF switches # NIL
	THEN {
	  i: CARDINAL;
	  
	  CompleteFileName: PROC RETURNS [fileName: STRING, bcpl: BOOLEAN] = {
	    OPEN String;
	    root: STRING ← [40];
	    extension: STRING ← [40];
	    SetRoot[root, sourceName];
	    IF root.length = sourceName.length
	      THEN  AppendString[extension, "image"L]
	      ELSE
		FOR j: CARDINAL IN [root.length+1 .. sourceName.length)
		  DO  AppendChar[extension, sourceName[j]]  ENDLOOP;
	    fileName ← CommandUtil.CopyString[root, 2+extension.length];
	    fileName ← CommandUtil.SetExtension[fileName, extension];
	    bcpl ← EquivalentString[extension, "run"L];  RETURN};
	    
	  WriteCommandFile: PROC [fileName: STRING] = {
	    copy: Streams.Handle;
	    copy ← Streams.CreateStream[
			Segments.NewFile["com.cm."L, Segments.Write],
			Segments.Write];
	    FOR j: CARDINAL IN [0..fileName.length-1)
	      DO PutChar[copy, fileName[j]] ENDLOOP;
	    IF switches.length > i+1
	      THEN {
		PutChar[copy, '/];
		FOR j: CARDINAL IN (i..switches.length)
		  DO PutChar[copy, switches[j]] ENDLOOP};
	    IF Streams.Ended[commandStream]
	      THEN PutChar[copy, CR]
	      ELSE {
		PutChar[copy, ' ];
		UNTIL Streams.Ended[commandStream]
		  DO PutChar[copy, GetChar[commandStream]] ENDLOOP};
	    Streams.Destroy[copy]};
	    
	  Run: PROC [fileName: STRING, bcpl: BOOLEAN] = {
	    IF bcpl
	      THEN {
		p: POINTER = OsStaticDefs.OsStatics.EventVector;
		EVItem: TYPE = MACHINE DEPENDENT RECORD [
		type: [0..7777B], length: [0..17B]];
		p↑ ← EVItem[6, StringDefs.WordsForBcplString[fileName.length]+1];
		StringDefs.MesaToBcplString[fileName, p+1];
		ImageDefs.StopMesa[]}
	      ELSE
		ImageDefs.RunImage[Segments.NewSegment[
		  file: Segments.NewFile[fileName, Segments.Read],
		  base: 1, pages: 1,
		  access: Segments.Read]]};
		  
	  i ← 0;  sense ← TRUE;
	  WHILE i < switches.length
	    DO
	    c: CHARACTER = switches[i];
	    SELECT c FROM
	      '-, '~ => sense ← ~sense;
	      'r, 'R => {
		fileName: STRING;
		bcpl: BOOLEAN;
		[fileName, bcpl] ← CompleteFileName[];
		WriteCommandFile[fileName];
		StopCompiler[TRUE];
		Run[fileName, bcpl ! ANY => ImageDefs.AbortMesa]};
		-- never returns
	      'o, 'O => {
		[] ← CommandUtil.FreeString[headerComment]; GOTO headerLine};
	      IN ['a..'z] => {parms.switches[c] ← sense; sense ← TRUE};
	      IN ['A..'Z] => {
		parms.switches[c+('a-'A)] ← sense; sense ← TRUE};
	      '! => {MiscDefs.CallDebugger[NIL]; bitmapFrozen ← TRUE};
	      IN ['1..'5] => {parms.debugPass ← c-'0; sense ← TRUE};
	      ENDCASE;
	    i ← i+1;
	    ENDLOOP;
	  switches ← CommandUtil.FreeString[switches]};
	  
      parms.sourceId ← sourceName ← CommandUtil.SetExtension[sourceName, "mesa"];
      parms.fileMap ← args;
      
      IF CommandUtil.ListLength[results] # 0
	THEN {
	  IF ~formatting THEN
	    objectName ← CommandUtil.GetNth[list: results, n: 0, delete: TRUE];
	  results ← CommandUtil.FreePairList[results]}
	ELSE IF ~formatting THEN
	  objectName ← CommandUtil.CopyString[rootName, 2+("bcd").length];
      IF ~formatting THEN objectName ← CommandUtil.SetExtension[objectName, "bcd"];
      parms.objectId ← IF formatting THEN NIL ELSE objectName;
      formatterOverwrite ←
	parms.switches['t] OR parms.switches['v] OR parms.switches['z];
	
      BEGIN
      ENABLE ANY => GO TO punt;
      Segments.EnumerateDirectory[FindFiles];
      IF formatting AND formatterOverwrite AND sourceFile # NIL THEN {
	IF scratchFile = NIL THEN {
	  scratchFile ← Segments.NewFile["Formatter.scratch$."L, Segments.Write];
	  Segments.LockFile[scratchFile]};
	CopyFile[from: sourceFile, to: scratchFile];
	objectStream ← Streams.CreateStream[sourceFile, Segments.Write];
	objectFile ← sourceFile; Segments.SetFileTimes[objectFile];
	sourceFile ← scratchFile; scratchFile ← NIL};
      IF sourceFile = NIL THEN GO TO noSource;
      parms.sourceVersion ← [0, 0, Segments.GetFileTimes[sourceFile].create];
      sourceStream ← Streams.CreateStream[sourceFile, Segments.Read];
      Segments.UnlockFile[sourceFile];  sourceFile ← NIL;
      END;
      
      IF ~bitmapFrozen
	THEN { 
	  IF ~parms.switches['d] THEN MiscOps.ReleaseDebuggerBitmap[];
	  bitmapFrozen ← TRUE};
      NewLine[];
      moduleCount ← moduleCount + 1;
      
      MiniDisplay.Append[IF formatting THEN "Formatting: "L ELSE "Compiling: "L];
      MiniDisplay.Append[rootName];
      BEGIN
      cs: STRING ← [1];
      first: BOOLEAN ← TRUE;
      cs.length ← 1;
      FOR c: CHARACTER IN ['a..'z] DO
	sd: BOOLEAN = IF c = 'p THEN FALSE ELSE InitDefaults[c];
        IF parms.switches[c] # sd THEN {
	  IF first THEN {first ← FALSE; MiniDisplay.Append["/"L]};
	  IF sd THEN MiniDisplay.Append["~"L];
	  cs[0] ← c; 
	  MiniDisplay.Append[cs]};
	ENDLOOP;
      END;
      Initialize[];
      CompilerOps.CompileAction[parms ! CompilerOps.Punt => GO TO punt];
      ClearCursor[];
      MiniDisplay.Append[" -- "L];
      MiniDisplay.Append[IF parms.nErrors=0 THEN "ok"L ELSE "failed"L];
      Finalize[TRUE];  WriteClosing[];
      
      EXITS
	globalSwitches => {
	  objectName ← NIL;  results ← CommandUtil.FreePairList[results];
	  sense ← TRUE;
	  FOR i: CARDINAL IN [0..switches.length)
	    DO
	    c: CHARACTER = switches[i];
	    SELECT c FROM
	      '-, '~ => sense ← ~sense;
	      '! => {MiscDefs.CallDebugger[NIL]; bitmapFrozen ← TRUE};
	      'o, 'O => headerComment ← CommandUtil.FreeString[headerComment];
	      IN ['a..'z] => {SwitchDefaults[c] ← sense; sense ← TRUE};
	      IN ['A..'Z] => {SwitchDefaults[c+('a-'A)] ← sense; sense ← TRUE};
	      ENDCASE => EXIT;
	    ENDLOOP;
	  switches ← CommandUtil.FreeString[switches]};
	headerLine => {
	  headerComment ← sourceName; sourceName ← NIL;
	  objectName ← NIL;  results ← CommandUtil.FreePairList[results];
	  switches ← CommandUtil.FreeString[switches]};
	badSemantics => {
	  objectName ← NIL;  results ← CommandUtil.FreePairList[results];
	  errors ← TRUE;  PutString[log, " -- Illegal command"]};
	noSource => {
	  Finalize[FALSE];  errors ← TRUE;
	  PutChar[log, CR]; PutString[log, sourceName]; PutString[log, " -- File error"]};
      END;
    sourceName ← CommandUtil.FreeString[sourceName];
    IF ~formatting THEN objectName ← CommandUtil.FreeString[objectName];
    args ← CommandUtil.FreePairList[args];
    NewLine[];
    IF (errors OR warnings) AND parms.switches['p] THEN GO TO truncateList;
    REPEAT
      badSyntax => {
	NewLine[]; CharIO.PutString[log, "-- Illegal syntax"]; errors ← TRUE};
      truncateList => SwitchDefaults['p] ← TRUE;
      punt => {ClearCursor[]; Finalize[TRUE]; WriteClosing[]; NewLine[]};
    ENDLOOP;
    
  StopCompiler[];
  AltoDisplay.CursorXY↑ ← saveCoordinate;  AltoDisplay.Cursor↑ ← saveCursor;
  AltoDisplay.DCBchainHead↑ ← saveDCB;
  ImageDefs.StopMesa[];
  
  END.