-- NodeStyleExtraImpl.mesa
-- Written by Bill Paxton, January 1981
-- Last changed by Bill Paxton, 3-Jun-81 13:43:44

DIRECTORY
	NodeStyleExtra,
	NodeStyle,
	TextNode,
	TextLooks,
	TiogaJaM,
	Inline,
	JaMFnsDefs,
	JaMOtherDefs;

NodeStyleExtraImpl: PROGRAM
	IMPORTS TiogaJaM, JaMFnsDefs, JaMOtherDefs,
		NodeStyle, NodeStyleExtra, Inline
	EXPORTS NodeStyle, NodeStyleExtra =
BEGIN
OPEN NodeStyle, NodeStyleExtra, JaMFnsDefs,
	tjI:TiogaJaM,
	nodeI:TextNode,
	looksI:TextLooks,
	jamI:JaMOtherDefs;

-- Style Name

styleName: nodeI.StyleName ← nodeI.nullStyleName; -- the current style name

CurrentStyle: PUBLIC PROC RETURNS [nodeI.StyleName] =
	{ RETURN [styleName] };

SetStyle: PUBLIC PROC [name: nodeI.StyleName] = {
	IF name = styleName THEN RETURN;
	IF styleName # nodeI.nullStyleName THEN ExecuteCommand[end];
	PushObject[GetStyleDict[name]]; 
	ExecuteCommand[begin];
	styleName ← name;
	};


-- Load style procedures

LoadStyle: PUBLIC PROC [name: nodeI.StyleName] = { [] ← GetStyleDict[name] };

GetStyleDict: PROC [name: nodeI.StyleName] RETURNS [d: Object] = {
	found: BOOLEAN;
	[d, found] ← CheckStyleDict[name];
	IF found THEN RETURN;
	d ← CreateStyleDict[name];
	RunStyle[d, name, ".tes", TRUE];
	EnterStyleDict[name, d];
	};

ReloadStyle: PUBLIC PROC [name: nodeI.StyleName] = {
	d: Object;
	found: BOOLEAN;
	[d, found] ← CheckStyleDict[name];
	IF found THEN {
		PushObject[d]; ExecuteCommand[clrdict];
		PushObject[d]; ExecuteCommand[detachall] }
	ELSE d ← CreateStyleDict[name];
	RunStyle[d, name, ".tes", TRUE];
	IF ~found THEN EnterStyleDict[name, d];
	ClearLooksCache[]; ClearRuleCache[]};

CreateStyleDict: PROC [name: nodeI.StyleName] RETURNS [d: Object] = {
	-- creates dict for style and enters it in stylesDict
	PushInteger[20];
	ExecuteCommand[dict];
	d ← PopObject[]};

EnterStyleDict: PROC [name: nodeI.StyleName, d: Object] = {
	PushObject[stylesDict];
	PushName[tjI.StyleToJaM[name]];
	PushObject[d];
	ExecuteCommand[put];
	};

CheckStyleDict: PROC [name: nodeI.StyleName]
	RETURNS [d: Object, found: BOOLEAN] = {
	PushObject[stylesDict];
	PushName[tjI.StyleToJaM[name]];
	ExecuteCommand[known];
	IF (found ← PopBoolean[]) THEN {
		PushObject[stylesDict]; PushName[tjI.StyleToJaM[name]];
		ExecuteCommand[get]; d ← PopObject[] };
	};

RunStyle: PROC
	[d: Object, name: nodeI.StyleName, ext: REF TEXT, go: BOOLEAN] = {
	txt: REF TEXT ← NEW[TEXT[64]];
	txtlen: NAT;
	jamI.TextForName[LOOPHOLE[txt], LOOPHOLE[name] !
		jamI.TextOverflow =>
			RESUME[LOOPHOLE[txt ← NEW[TEXT[txt.maxLength*2]]]]];
	txtlen ← txt.length;
	FOR i:NAT IN [0..ext.length) DO txt[txtlen+i] ← ext[i]; ENDLOOP;
	txt.length ← txtlen+ext.length;
	PushCommand[end];
	PushCommand[run];
	PushName[MakeName[txt]];
	PushCommand[begin];
	PushObject[d];
	IF go THEN jamI.Go[];
	};

Apply: PUBLIC PROC
	[ref: Ref, name, alt: nodeI.TypeName ← nodeI.nullTypeName] = {
	initloc, loc: NAT;
	input: Body;
	IF name = nodeI.nullTypeName THEN RETURN;
	loc ← initloc ←
		Inline.BITXOR[LOOPHOLE[name,CARDINAL],Hash[ref]] MOD ruleCacheSize;
	DO -- search cache
		SELECT ruleCacheNames[loc] FROM
			name => IF ruleCacheInputs[loc] = ref↑ THEN
				{ ref↑ ← ruleCacheResults[loc]; RETURN };
			nodeI.nullTypeName => EXIT; -- this is an unused entry
			ENDCASE;
		SELECT (loc ← loc+1) FROM
			ruleCacheSize => IF (loc ← 0)=initloc THEN EXIT;
			initloc => EXIT;
			ENDCASE;
		ENDLOOP;
	IF ruleCacheCount = ruleCacheMax THEN {
		loc ← initloc; ClearRuleCache[] };
	SetStyle[ref.styleName]; -- make style current
	style ← ref; -- make it current
	input ← ref↑;
	IF ExecuteName[tjI.TypeToJaM[name]] THEN { -- save results in cache
		ruleCacheCount ← ruleCacheCount+1;
		ruleCacheInputs[loc] ← input;
		ruleCacheResults[loc] ← ref↑;
		ruleCacheNames[loc] ← name }
	ELSE IF alt # nodeI.nullTypeName THEN [] ← ExecuteName[tjI.TypeToJaM[alt]];
	};

ruleCacheSize: NAT = 128; -- should be a power of 2
ruleCacheMax: NAT = (ruleCacheSize*2)/3; -- don't fill too full
ruleCacheCount: NAT; -- number of entries currently in use
RuleCacheNames: TYPE = ARRAY [0..ruleCacheSize) OF nodeI.TypeName;
ruleCacheNames: REF RuleCacheNames ← NEW[RuleCacheNames];
RuleCacheBodies: TYPE = ARRAY [0..ruleCacheSize) OF Body;
ruleCacheInputs: REF RuleCacheBodies ← NEW[RuleCacheBodies];
ruleCacheResults: REF RuleCacheBodies ← NEW[RuleCacheBodies];

ClearRuleCache: PROC = {
	ruleCacheCount ← 0;
	FOR i: NAT IN [0..ruleCacheSize) DO
		ruleCacheNames[i] ← nodeI.nullTypeName; ENDLOOP;
	};

Hash: PROC [ref: Ref] RETURNS [CARDINAL] = INLINE { RETURN [
	LOOPHOLE[
		Inline.BITXOR[LOOPHOLE[ref.styleName,CARDINAL],
			Inline.BITXOR[LOOPHOLE[ref.fontFamily,CARDINAL],
				Inline.BITXOR[Inline.LowHalf[ref.fontSize],
					Inline.BITXOR[Inline.LowHalf[ref.leftIndent],
						Inline.LowHalf[ref.leading]]]]],CARDINAL]] };

ApplyLooks: PUBLIC PROC [ref: Ref, looks: looksI.Looks] = {
	initloc, loc: NAT;
	IF looks = looksI.noLooks THEN RETURN;
	loc ← initloc ←
		Inline.BITXOR[LOOPHOLE[looks, looksI.LooksBytes].byte0,
			Inline.BITXOR[LOOPHOLE[looks, looksI.LooksBytes].byte1,
				Inline.BITXOR[LOOPHOLE[looks, looksI.LooksBytes].byte2,
					Hash[ref]]]] MOD looksCacheSize;
	DO -- search cache
		SELECT looksCacheLooks[loc] FROM
			looks => IF looksCacheInputs[loc] = ref↑ THEN
				{ ref↑ ← looksCacheResults[loc]; RETURN };
			looksI.noLooks => EXIT; -- this is an unused entry
			ENDCASE;
		SELECT (loc ← loc+1) FROM
			looksCacheSize => IF (loc ← 0)=initloc THEN EXIT;
			initloc => EXIT;
			ENDCASE;
		ENDLOOP;
	IF looksCacheCount = looksCacheMax THEN {
		loc ← initloc; ClearLooksCache[] };
	SetStyle[ref.styleName]; -- make style current
	style ← ref; -- make it current
	looksCacheLooks[loc] ← looks;
	looksCacheInputs[loc] ← ref↑;
	FOR c: CHARACTER IN looksI.Look DO
		IF looks[c] THEN [] ← ExecuteName[lookNames[c]]
		ENDLOOP;
	looksCacheResults[loc] ← ref↑;
	looksCacheCount ← looksCacheCount+1;
	};

looksCacheSize: NAT = 128; -- should be a power of 2
looksCacheMax: NAT = (looksCacheSize*2)/3; -- don't fill too full
looksCacheCount: NAT; -- number of entries currently in use
LooksCacheLooks: TYPE = ARRAY [0..looksCacheSize) OF looksI.Looks;
looksCacheLooks: REF LooksCacheLooks ← NEW[LooksCacheLooks];
LooksCacheBodies: TYPE = ARRAY [0..looksCacheSize) OF Body;
looksCacheInputs: REF LooksCacheBodies ← NEW[LooksCacheBodies];
looksCacheResults: REF LooksCacheBodies ← NEW[LooksCacheBodies];

ClearLooksCache: PROC = {
	looksCacheCount ← 0;
	FOR i: NAT IN [0..looksCacheSize) DO
		looksCacheLooks[i] ← looksI.noLooks; ENDLOOP;
	};


-- Registered commands

StyleDefOp: PROC = { -- does bindingDict .abind .cvx .def
	PushObject[bindingDict];
	ExecuteCommand[abind];
	ExecuteCommand[cvx];
	ExecuteCommand[def];
	};

SubStyleOp: PROC = { -- expects opstk to contain style name
	found: BOOLEAN;
	name: nodeI.StyleName ← tjI.JaMToStyle[PopName[]];
	d: Object;
	[d, found] ← CheckStyleDict[name];
	IF ~found THEN d ← CreateStyleDict[name];
	PushObject[d]; ExecuteCommand[attachdict];
	IF ~found THEN {
		PushName[tjI.StyleToJaM[name]]; PushObject[d];
		PushCommand[finishSubStyle];
		RunStyle[d, name, ".tes", FALSE] };
	};

FinishSubStyle: PROC = { -- .run finished successfully
	d: Object ← PopObject[];
	EnterStyleDict[tjI.JaMToStyle[PopName[]], d] };

BadFileName: PROC = { -- ???? what should we do ????
	-- this comes from giving .run a file name it cannot open
	ERROR StyleError };

-- Dimensions

Points: PROC = { }; -- no change needed to convert to points

PointsPerPica: REAL = 12.0;
Picas: PROC = { PushReal[PopReal[]*PointsPerPica] };

PointsPerInch: REAL = 1.0/0.0138370; -- 72.27
Inches: PROC = { PushReal[PopReal[]*PointsPerInch] };

PointsPerCentimeter: REAL = PointsPerInch/2.540;
Centimeters: PROC = { PushReal[PopReal[]*PointsPerCentimeter] };

PointsPerMillimeter: REAL = PointsPerCentimeter/10;
Millimeters: PROC = { PushReal[PopReal[]*PointsPerMillimeter] };

PointsPerDidot: REAL = PointsPerCentimeter/26.60;
DidotPoints: PROC = { PushReal[PopReal[]*PointsPerDidot] };

Ems: PROC = { PushReal[PopReal[]*style.fontSize] };
	-- should really be width of "M" in current font
	-- use font size as an approximation for now


-- Initialization

lookNames: ARRAY looksI.Look OF tjI.JaMName; 

stylesDictName, bindingDictName: tjI.JaMName;

stylesDict, bindingDict: Object;

StyleCommand: PUBLIC PROC [text: REF TEXT, proc: PROC] = {
	name: tjI.JaMName ← MakeName[text];
	jamI.RegisterCommand[LOOPHOLE[text], proc];
	-- add it to the binding dictionary
	PushObject[bindingDict];
	PushName[name];
	PushName[name];
	ExecuteCommand[load];
	ExecuteCommand[cvx];
	ExecuteCommand[put]};

StyleLiteral: PUBLIC PROC [text: REF TEXT] RETURNS [name: tjI.JaMName] = {
	name ← MakeName[text];
	-- add it to the binding dictionary
	PushObject[bindingDict];
	PushName[name];
	PushName[name];
	ExecuteCommand[cvlit];
	ExecuteCommand[put];
	-- add it to the current dictionary
	PushName[name];
	PushName[name];
	ExecuteCommand[cvlit];
	ExecuteCommand[def]};

InitStylesDict: PROC = {
	stylesDictName ← MakeName["TiogaEditorStylesDictionary"];
	PushName[stylesDictName];
	ExecuteCommand[where];
	IF PopBoolean[] THEN {
		PushName[stylesDictName]; ExecuteCommand[get];
		stylesDict ← PopObject[] }
	ELSE {
		PushInteger[20]; ExecuteCommand[dict];
		stylesDict ← PopObject[];
		PushName[stylesDictName]; PushObject[stylesDict];
		ExecuteCommand[def] }};

InitBindingDict: PROC = {
	bindingDictName ← MakeName["TiogaEditorBindingDictionary"];
	PushName[bindingDictName];
	ExecuteCommand[where];
	IF PopBoolean[] THEN {
		PushName[bindingDictName]; ExecuteCommand[get];
		bindingDict ← PopObject[];
		PushObject[bindingDict]; ExecuteCommand[clrdict] }
	ELSE {
		PushInteger[100]; ExecuteCommand[dict];
		bindingDict ← PopObject[];
		PushName[bindingDictName]; PushObject[bindingDict];
		ExecuteCommand[def];
		PushObject[bindingDict] }};

InitLookNames: PROC = {
	-- names are "aLook", "bLook", "cLook", etc.
	txt: REF TEXT ← NEW[TEXT[5]];
	txt[1] ← 'L; txt[2] ← txt[3] ← 'o; txt[4] ← 'k; txt.length ← 5;
	FOR c: CHARACTER IN looksI.Look DO
		txt[0] ← c; lookNames[c] ← MakeName[txt]; ENDLOOP};

cvlit, cvx, def, put, get, dict, attachdict, detachall, abind,
	begin, end, run, load, clrdict, where, known, assign, finishSubStyle:
	PUBLIC Command;

GetCommand: PUBLIC PROC [name: tjI.JaMName] RETURNS [c: Command] = {
	flag: BOOLEAN;
	[c, flag] ← jamI.TryToGetCommand[LOOPHOLE[name]];
	IF ~flag THEN ERROR };

StartExtra: PUBLIC PROCEDURE =
	BEGIN

	cvlit ← GetCommand[MakeName[".cvlit"]];
	cvx ← GetCommand[MakeName[".cvx"]];
	def ← GetCommand[MakeName[".def"]];
	put ← GetCommand[MakeName[".put"]];
	get ← GetCommand[MakeName[".get"]];
	dict ← GetCommand[MakeName[".dict"]];
	attachdict ← GetCommand[MakeName[".attachdict"]];
	detachall ← GetCommand[MakeName[".detachall"]];
	abind ← GetCommand[MakeName[".abind"]];
	where ← GetCommand[MakeName[".where"]];
	begin ← GetCommand[MakeName[".begin"]];
	end ← GetCommand[MakeName[".end"]];
	run ← GetCommand[MakeName[".run"]];
	load ← GetCommand[MakeName[".load"]];
	clrdict ← GetCommand[MakeName[".clrdict"]];
	known ← GetCommand[MakeName[".known"]];
	assign ← GetCommand[MakeName[".assign"]];

	InitStylesDict[]; InitBindingDict[]; InitLookNames[];
	
	JaMFnsDefs.Register[LOOPHOLE["FinishSubStyle"],FinishSubStyle];
	finishSubStyle ← GetCommand[MakeName["FinishSubStyle"]];
	
	StyleCommand[".badfilename",BadFileName];
	StyleCommand["StyleDef",StyleDefOp];
	StyleCommand["SubStyle",SubStyleOp];

	StyleCommand["pt",Points];
	StyleCommand["pc",Picas];
	StyleCommand["in",Inches];
	StyleCommand["cm",Centimeters];
	StyleCommand["mm",Millimeters];
	StyleCommand["dd",DidotPoints];
	StyleCommand["em",Ems];

	ClearLooksCache[]; ClearRuleCache[];

	END;

END.