-- SExecImpl.mesa; Edited by McGregor on April 12, 1982 1:29 pm

DIRECTORY
  CVExecutive USING [CommandProc],
  Inline USING [LowHalf],
  IOStream USING [Close, CreateFileStream, Error, GetBlock, GetLength, Handle, PutFR, time],
  LongString,     -- <APilot>ComSoft>Public>
  Menus USING [AppendMenuEntry, MenuProc],
  PilotLoadStateOps,    -- <CedarLang>Loader>
  PrincOps,     -- <APilot>Mesa>Public>
  Process USING [Detach],
  Rope USING [Compare, Concat, Fetch, Ref, Size, Substr, Text],
  RopeInline USING [NewText],
  Runtime USING [GetBcdTime],
  SExecOps,
  TypeScript USING [CharProc, Create, GetLine, PutChar, PutRope, TS],
  UserProfile USING [String],
  UserTerminal USING [BlinkDisplay],
  ViewerTools USING [GetSelectedViewer, GetSelectionContents, SetSelection];

SExecImpl: PROGRAM

IMPORTS Inline, IOStream, Menus, Process, Rope, RopeInline, Runtime, SExecOps,
	TypeScript, UserProfile, UserTerminal, ViewerTools
EXPORTS CVExecutive, SExecOps =

BEGIN OPEN TypeScript;

maxKeys: CARDINAL = 32;
nKeys: CARDINAL ← 0;

Command: TYPE = RECORD [
	key: Rope.Ref,
	proc: CVExecutive.CommandProc,
	doc: Rope.Ref
	];

commands: ARRAY [0..maxKeys) OF Command;

AddCommand: PUBLIC PROC [key: Rope.Ref, proc: CVExecutive.CommandProc,
	doc: Rope.Ref] = BEGIN
	IF nKeys<maxKeys THEN BEGIN
		commands[nKeys] ← [key, proc, doc];
		nKeys ← nKeys+1;
		END
	ELSE ERROR;	-- too many commands!
	END;

GetToken: PUBLIC PROCEDURE [rope: Rope.Ref, offset: LONG INTEGER ← 0, thruEnd: BOOLEAN ← FALSE]
    RETURNS [token: Rope.Ref, newOffset: LONG INTEGER] = BEGIN
	size: LONG INTEGER = Rope.Size[rope];
	start: LONG INTEGER;
	char: CHARACTER;
	
	Blank: PROC [c: CHARACTER] RETURNS [BOOLEAN] = INLINE BEGIN
		RETURN[SELECT c FROM
			40C, 11C, 15C, 33C, 0C	=> TRUE,
			ENDCASE			=> FALSE];
		END;

	Break: PROC [c: CHARACTER] RETURNS [BOOLEAN] = INLINE BEGIN
		RETURN[SELECT c FROM
			';, 15C		=> TRUE,
			ENDCASE		=> FALSE];
		END;

	IF offset>=size THEN RETURN["", offset];
	WHILE offset<size AND Blank[Rope.Fetch[rope, offset]] DO
		offset ← offset+1;
		ENDLOOP;
	start ← offset;
	DO IF offset>=size THEN EXIT;
		char ← Rope.Fetch[rope, offset];
		IF Break[char] THEN BEGIN
			IF offset=start THEN RETURN[";", offset+1] ELSE EXIT;
			END;
		IF Blank[char] THEN EXIT;
		offset ← offset+1;
		ENDLOOP;
	RETURN[Rope.Substr[rope, start, IF thruEnd THEN size
		ELSE offset-start], offset];
	END;

currentParams: PUBLIC Rope.Ref;

Line: PROC [line: Rope.Ref, ts: TS] = BEGIN OPEN Rope;
	cmd, token: Ref ← NIL;
	offset: LONG INTEGER ← 0;
	first: BOOLEAN ← TRUE;
	didSomething: BOOLEAN ← TRUE;
	IF Size[line] > 0 THEN BEGIN
		WHILE didSomething DO [line, didSomething] ← Expand[line]; ENDLOOP;
		DO	[token, offset] ← GetToken[line, offset];
			IF Size[token] = 0 THEN BEGIN
				IF Size[cmd]#0 THEN ProcessCommand[cmd, ts];
				EXIT;
				END;
			IF Fetch[token, 0] = '; THEN BEGIN
				IF Size[cmd]#0 THEN ProcessCommand[cmd, ts];
				cmd ← NIL;
				first ← TRUE;
				LOOP;
				END;
			IF first THEN first ← FALSE ELSE cmd ← Concat[cmd, " "];
			cmd ← Concat[cmd, token];
			ENDLOOP;
		END;
	PutChar[ts, '@];
	END;

Expand: PROC [line: Rope.Ref] RETURNS [newLine: Rope.Ref, didSomething: BOOLEAN] =
	BEGIN OPEN Rope;
	token: Ref ← NIL;
	offset: LONG INTEGER ← 0;
	first: BOOLEAN ← TRUE;
	didSomething ← FALSE;
	IF Size[line] > 0 THEN DO
		[token, offset] ← GetToken[line, offset];
		IF Size[token] = 0 THEN RETURN;
		IF Fetch[token, 0] = '; THEN BEGIN
			first ← TRUE;
			newLine ← Concat[newLine, ";"];
			LOOP;
			END;
		IF first THEN first ← FALSE ELSE newLine ← Concat[newLine, " "];
		IF Fetch[token, 0] = '@ THEN BEGIN
			exp: Ref ← IF Size[token]<2 THEN "" ELSE
			    RopeFromFile[Substr[token, 1, Size[token]-1], anExecTS];
			IF exp = NIL THEN EXIT ELSE token ← exp;
			didSomething ← TRUE;
			END;
		newLine ← Concat[newLine, token];
		ENDLOOP;
	END;

RopeFromFile: PROC [file: Rope.Ref, ts: TypeScript.TS]
    RETURNS [contents: Rope.Ref] = BEGIN
	fh: IOStream.Handle;
	found: BOOLEAN ← TRUE;
	length: LONG INTEGER;
	text: Rope.Text;
	fh ← IOStream.CreateFileStream[file, read, oldOnly
		! IOStream.Error	=> {found ← FALSE; CONTINUE}];
	IF ~found THEN BEGIN
		found ← TRUE;
		fh ← IOStream.CreateFileStream[Rope.Concat[file, ".cm"], read, oldOnly
		    ! IOStream.Error	=> {found ← FALSE; CONTINUE}];
		END;
	IF ~found THEN BEGIN
		PutRope[ts, "Couldn't expand: "];
		PutRope[ts, file];
		PutChar[ts, 15C];
		RETURN[NIL];
		END;
	length ← IOStream.GetLength[fh];
	IF length > 10000 THEN BEGIN
		PutRope[ts, "File expansion is too long: "];
		PutRope[ts, file];
		PutChar[ts, 15C];
		IOStream.Close[fh];
		RETURN[NIL];
		END;
	text ← RopeInline.NewText[Inline.LowHalf[length]];
	[] ← IOStream.GetBlock[fh, LOOPHOLE[text]];
	IOStream.Close[fh];
	RETURN[text];
	END;

ProcessCommand: PROC [cmd: Rope.Ref, ts: TypeScript.TS] = BEGIN OPEN Rope;
	token: Ref;
	length, parseOffset: LONG INTEGER ← 0;
	[token, parseOffset] ← GetToken[cmd];
	length ← Size[token];
	[currentParams, ----] ← GetToken[cmd, parseOffset, TRUE];
	FOR n: CARDINAL IN [0..nKeys) DO
		IF Compare[token, Substr[commands[n].key, 0,
		    MIN[length, Size[commands[n].key]]], FALSE]=0 THEN
			{commands[n].proc[ts, currentParams]; EXIT};
		REPEAT FINISHED =>
			IF length>4 AND Compare[Substr[token, length-4, 4],".bcd",FALSE]=0
				THEN SExecOps.Run[ts, token]	-- run as a program
			ELSE IF SExecOps.CheckForFile[Rope.Concat[token, ".bcd"]]
				THEN SExecOps.Run[ts, Rope.Concat[token, ".bcd"]]
			ELSE SExecOps.Old[ts, token];	-- create new viewer
		ENDLOOP;
	PutChar[ts, 15C];
	END;

Help: PUBLIC CVExecutive.CommandProc = BEGIN
	PutRope[ts, "This exec expects a command followed by a list of arguments.  When you type something that isn't recognised as a command keyword, the executive will try to run it as a program, otherwise it will try to create a new viewer using the name as a backing file.  Current commands registered:
"];
	FOR n: CARDINAL IN [0..nKeys) DO
		PutRope[ts, commands[n].key];
		IF Rope.Size[commands[n].key] < 3 THEN PutChar[ts, '	];
		IF Rope.Size[commands[n].key] < 7 THEN PutChar[ts, '	];
		PutRope[ts, "	-- "];
		PutRope[ts, commands[n].doc];
		IF nKeys-n>1 THEN PutChar[ts, 15C];
		ENDLOOP;
	END;


LineEval: PROC = BEGIN
	DO Line[GetLine[anExecTS], anExecTS]; ENDLOOP;
	END;

CompileHack: Menus.MenuProc = BEGIN
	sel: Rope.Ref ← ViewerTools.GetSelectionContents[];
	IF sel = NIL OR Rope.Size[sel] > 30 THEN {UserTerminal.BlinkDisplay[]; RETURN};
	IF Rope.Size[sel] <= 1 THEN sel ← ViewerTools.GetSelectedViewer[].name;
	sel ← Rope.Concat["Compile ", sel];
	sel ← Rope.Concat[sel, "
"];
	IF ViewerTools.GetSelectedViewer[] # anExecTS THEN
		ViewerTools.SetSelection[anExecTS, NIL];
	anExecTS.class.notify[anExecTS, LIST[sel]];
	END;

UserName: PUBLIC Rope.Ref ← UserProfile.String["User"];
UserPassword: PUBLIC Rope.Ref ← NIL;

version: Rope.Ref = IOStream.PutFR["SExec of %t", IOStream.time[Runtime.GetBcdTime[]]];

anExecTS: PUBLIC TS ← Create["SExec", TRUE];

Menus.AppendMenuEntry[menu: anExecTS.menu, name: "Compile", proc: CompileHack, copy: TRUE];

PutRope[anExecTS, version];
PutRope[anExecTS, "; Type '?' for help.
@"];

Process.Detach[FORK LineEval];

END.