<> <> <> <> <> <<>> DIRECTORY Basics, NodeStyleExtra, NodeStyle, NodeStyleObject, TextNode, TextLooks, EditNotify, NodeProps, NameSymbolTable, JaMOps; NodeStyleApplyImpl: CEDAR MONITOR IMPORTS Basics, JaMOps, TextNode, EditNotify, NodeProps, NodeStyle, NodeStyleExtra, NameSymbolTable EXPORTS NodeStyle, NodeStyleExtra = BEGIN OPEN NodeStyle, NodeStyleExtra; <<-- Apply operations>> defaultStyle: PUBLIC Ref; defaultName, rootName: Name; ApplyAll: PUBLIC PROC [ref: Ref, node: TextNode.Ref, kind: OfStyle _ screen] = { [] _ DoApplyAll[ref,node,kind] }; DoApplyAll: PROC [ref: Ref, node: TextNode.Ref, kind: OfStyle] RETURNS [depth: CARDINAL] = { found: BOOLEAN; parent: TextNode.Ref; alt: Name; IF node=NIL THEN { ref^ _ defaultStyle^; RETURN [0] }; [found,depth] _ FindInApplyAllCache[ref,node,kind]; IF found THEN RETURN [depth+1]; parent _ TextNode.Parent[node]; alt _ IF parent=NIL THEN rootName ELSE defaultName; depth _ DoApplyAll[ref,parent,kind]; ApplyForNode[ref,node,alt,kind]; EnterInApplyAllCache[ref,node,depth]; RETURN [depth+1] }; applyCacheInfo: REF ApplyCacheRecord _ TextNode.pZone.NEW[ApplyCacheRecord]; ApplyCacheRecord: TYPE = RECORD [ applyCacheDepth: CARDINAL _ 0, -- next free entry applyCacheResults: REF ApplyCacheResults, applyCacheNodes: REF ApplyCacheNodes, applyCacheProbes, applyCacheHits, applyCacheSaves: INT _ 0 ]; applyCacheSize: CARDINAL = 8; -- number of levels deep in tree ApplyCacheNodes: TYPE = ARRAY [0..applyCacheSize) OF TextNode.Ref; ApplyCacheResults: TYPE = ARRAY [0..applyCacheSize) OF StyleBody; InitApplyCacheRecord: PROC = { OPEN applyCacheInfo; applyCacheResults _ TextNode.pZone.NEW[ApplyCacheResults]; applyCacheNodes _ TextNode.pZone.NEW[ApplyCacheNodes] }; RemoveAllFromApplyAllCache: PUBLIC PROC = { FlushApplyAllCache[] }; FlushApplyAllCache: PUBLIC ENTRY PROC [init: BOOLEAN _ FALSE] = { ENABLE UNWIND => NULL; ClearApplyAllCache[init] }; ClearApplyAllCache: PROC [init: BOOLEAN] = { OPEN applyCacheInfo; nodes: REF ApplyCacheNodes _ applyCacheNodes; <<-- when clearing, go all the way to applyCacheSize rather than stopping at applyCacheDepth>> FOR i:CARDINAL IN [0..applyCacheSize) DO nodes[i] _ NIL; ENDLOOP; applyCacheDepth _ 0 }; RemoveNodeFromApplyAllCache: PUBLIC ENTRY PROC [node: TextNode.Ref] = { OPEN applyCacheInfo; ENABLE UNWIND => NULL; nodes: REF ApplyCacheNodes _ applyCacheNodes; FOR i:CARDINAL IN [0..applyCacheDepth) DO IF nodes[i]=node THEN { -- clear from here on FOR j:CARDINAL IN [i..applyCacheSize) DO nodes[j] _ NIL; ENDLOOP; applyCacheDepth _ i; EXIT }; ENDLOOP }; FindInApplyAllCache: ENTRY PROC [ref: Ref, node: TextNode.Ref, kind: OfStyle] RETURNS [found: BOOLEAN, depth: CARDINAL] = { OPEN applyCacheInfo; ENABLE UNWIND => NULL; nodes: REF ApplyCacheNodes _ applyCacheNodes; print: BOOL = (kind=print); -- if true, then find result with print true also applyCacheProbes _ applyCacheProbes+1; FOR i:CARDINAL DECREASING IN [0..applyCacheDepth) DO IF nodes[i]=node AND print=applyCacheResults[i].print THEN { -- found it applyCacheHits _ applyCacheHits+1; applyCacheSaves _ applyCacheSaves+i+1; ref^ _ applyCacheResults[i]; RETURN [TRUE, i] }; ENDLOOP; RETURN [FALSE, 0] }; EnterInApplyAllCache: ENTRY PROC [ref: Ref, node: TextNode.Ref, depth: CARDINAL] = { OPEN applyCacheInfo; ENABLE UNWIND => NULL; nodes: REF ApplyCacheNodes _ applyCacheNodes; IF depth >= applyCacheSize THEN RETURN; nodes[depth] _ node; applyCacheResults[depth] _ ref^; FOR i:CARDINAL IN [depth+1..applyCacheSize) DO nodes[i] _ NIL; ENDLOOP; applyCacheDepth _ depth+1 }; prefixAtom: ATOM = NodeProps.PrefixAtom[]; postfixAtom: ATOM = NodeProps.PostfixAtom[]; Notify: PROC [change: REF READONLY EditNotify.Change] = TRUSTED { <<-- if change invalidates one node only, remove that node>> <<-- else clear entire cache>> DoNode: PROC [node: TextNode.Ref] = TRUSTED { IF TextNode.FirstChild[node] # NIL THEN FlushApplyAllCache ELSE RemoveNodeFromApplyAllCache[node] }; WITH x:change SELECT FROM InsertingNode => IF TextNode.FirstChild[x.new] # NIL THEN FlushApplyAllCache; MovingNodes => FlushApplyAllCache; NodeNesting => IF x.first = x.last -- only changing one node AND TextNode.FirstChild[x.first] = NIL -- node has no children THEN SELECT x.change FROM 1 => -- increasing nesting in tree IF TextNode.Next[x.first] = NIL THEN RemoveNodeFromApplyAllCache[x.first] ELSE FlushApplyAllCache; -1 => -- decreasing nesting in tree RemoveNodeFromApplyAllCache[x.first]; ENDCASE => FlushApplyAllCache ELSE FlushApplyAllCache; ChangingType => DoNode[x.node]; ChangingProp => SELECT x.propAtom FROM prefixAtom, postfixAtom, $Comment, $StyleDef => DoNode[x.node]; ENDCASE; ENDCASE => ERROR; -- not expecting notify for any other kinds of changes }; ApplyForNode: PUBLIC PROC [ref: Ref, node: TextNode.Ref, alt: Name, kind: OfStyle] = { text: TextNode.RefTextNode = TextNode.NarrowToTextNode[node]; ext: ATOM; ref.isComment _ IF text # NIL THEN text.comment ELSE FALSE; ref.print _ (kind=print); ref.nestingLevel _ MIN[TextNode.Level[node], MaxNestingLevel]; EvalFreeVars[ref, node]; IF node.hasstyledef THEN { localStyle: LocalStyle _ NARROW[NodeProps.GetProp[node, $StyleDef]]; IF localStyle # NIL THEN ref.name[style] _ localStyle.name }; IF node.hasprefix THEN ApplyObject[ref, NodeProps.GetPrefixObject[node], kind] ELSE IF ref.nestingLevel=0 -- root node -- AND -- check for file extension default (ext _ NARROW[NodeProps.GetProp[node, $FileExtension]])#NIL THEN FOR list: LIST OF ExtObjPair _ defaultStylesForExtensions, list.rest UNTIL list=NIL DO IF list.first.fileExtension # ext THEN LOOP; ApplyObject[ref, list.first.styleObject, kind]; EXIT; ENDLOOP; ApplyType[ref, node.typename, alt, kind]; IF node.haspostfix THEN ApplyObject[ref,NodeProps.GetPostfixObject[node],kind] }; ApplyType: PUBLIC PROC [ref: Ref, name, alt: Name, kind: OfStyle _ screen] = { OPEN ruleCacheInfo; names: REF RuleCacheNames _ ruleCacheNames; inputs: REF RuleCacheBodies _ ruleCacheInputs; input: StyleBody; initloc, loc: CARDINAL; FindInRuleCache: ENTRY PROC RETURNS [BOOLEAN] = { ENABLE UNWIND => NULL; ruleCacheProbes _ ruleCacheProbes+1; DO -- search cache SELECT names[loc] FROM name => IF inputs[loc] = ref^ THEN { ref^ _ ruleCacheResults[loc]; ruleCacheHits _ ruleCacheHits+1; RETURN [TRUE] }; TextNode.nullTypeName => RETURN [FALSE]; -- this is an unused entry ENDCASE; SELECT (loc _ loc+1) FROM ruleCacheSize => IF (loc _ 0)=initloc THEN RETURN [FALSE]; initloc => RETURN [FALSE]; ENDCASE; ENDLOOP }; PutInRuleCache: ENTRY PROC = { ENABLE UNWIND => NULL; IF ruleCacheCount = ruleCacheMax THEN ClearRuleCache[]; loc _ initloc; DO -- search cache for place to put the entry SELECT names[loc] FROM name => IF inputs[loc] = input THEN RETURN; -- already in cache TextNode.nullTypeName => EXIT; -- this is an unused entry ENDCASE; SELECT (loc _ loc+1) FROM ruleCacheSize => IF (loc _ 0)=initloc THEN ERROR; -- cache full initloc => ERROR; -- cache full ENDCASE; ENDLOOP; ruleCacheCount _ ruleCacheCount+1; inputs[loc] _ input; ruleCacheResults[loc] _ ref^; names[loc] _ name }; ok: BOOLEAN; frame: Frame _ NIL; styleName: Name; IF name = TextNode.nullTypeName AND (name _ alt) = TextNode.nullTypeName THEN RETURN; loc _ initloc _ Basics.BITXOR[LOOPHOLE[name,CARDINAL],Hash[ref]] MOD ruleCacheSize; IF FindInRuleCache[] THEN RETURN; frame _ GetFrame[ref, styleName _ ref.name[style], kind]; input _ ref^; -- save the input value of the record ok _ ExecuteName[frame,name]; FreeFrame[frame,styleName,kind]; frame _ NIL; IF ok THEN PutInRuleCache[] -- save results in cache ELSE IF name # alt THEN ApplyType[ref,alt,TextNode.nullTypeName] }; ruleCacheInfo: REF RuleCacheInfoRecord _ TextNode.pZone.NEW[RuleCacheInfoRecord]; RuleCacheInfoRecord: TYPE = RECORD [ ruleCacheCount: CARDINAL _ 0, -- number of entries currently in use ruleCacheNames: REF RuleCacheNames, ruleCacheInputs: REF RuleCacheBodies, ruleCacheResults: REF RuleCacheBodies, ruleCacheProbes, ruleCacheHits: INT _ 0 ]; ruleCacheSize: CARDINAL = 64; -- should be a power of 2 ruleCacheMax: CARDINAL = (ruleCacheSize*4)/5; -- don't fill too full RuleCacheNames: TYPE = ARRAY [0..ruleCacheSize) OF TextNode.TypeName; RuleCacheBodies: TYPE = ARRAY [0..ruleCacheSize) OF StyleBody; InitRuleCacheInfo: PROC = { OPEN ruleCacheInfo; ruleCacheNames _ TextNode.pZone.NEW[RuleCacheNames]; ruleCacheInputs _ TextNode.pZone.NEW[RuleCacheBodies]; ruleCacheResults _ TextNode.pZone.NEW[RuleCacheBodies] }; FlushRuleCache: ENTRY PROC [init: BOOLEAN _ FALSE] = { ENABLE UNWIND => NULL; ClearRuleCache[] }; ClearRuleCache: PROC [init: BOOLEAN _ FALSE] = { OPEN ruleCacheInfo; names: REF RuleCacheNames _ ruleCacheNames; IF ~init AND ruleCacheCount = 0 THEN RETURN; ruleCacheCount _ 0; FOR i: CARDINAL IN [0..ruleCacheSize) DO names[i] _ TextNode.nullTypeName; ENDLOOP }; Hash: PROC [ref: Ref] RETURNS [CARDINAL] = { RETURN [LOOPHOLE[ Basics.BITXOR[LOOPHOLE[ref.name[style]], Basics.BITXOR[LOOPHOLE[ref.name[fontFamily],CARDINAL], Basics.BITXOR[ref.real[fontSize], Basics.BITXOR[ref.real[leftIndent], ref.real[leading]]]]],CARDINAL]] }; ApplyLooks: PUBLIC PROC [ref: Ref, looks: TextLooks.Looks, kind: OfStyle] = { OPEN looksCacheInfo; lks: REF LooksCacheLooks _ looksCacheLooks; inputs: REF LooksCacheBodies _ looksCacheInputs; initloc, loc: CARDINAL; input: StyleBody; FindInLooksCache: ENTRY PROC RETURNS [BOOLEAN] = { ENABLE UNWIND => NULL; looksCacheProbes _ looksCacheProbes+1; DO -- search cache SELECT lks[loc] FROM looks => IF inputs[loc] = ref^ THEN { ref^ _ looksCacheResults[loc]; looksCacheHits _ looksCacheHits+1; RETURN [TRUE] }; TextLooks.noLooks => RETURN [FALSE]; -- this is an unused entry ENDCASE; SELECT (loc _ loc+1) FROM looksCacheSize => IF (loc _ 0)=initloc THEN RETURN [FALSE]; initloc => RETURN [FALSE]; ENDCASE; ENDLOOP }; PutInLooksCache: ENTRY PROC = { ENABLE UNWIND => NULL; IF looksCacheCount = looksCacheMax THEN ClearLooksCache[]; loc _ initloc; DO -- search cache SELECT lks[loc] FROM looks => IF inputs[loc] = input THEN RETURN; -- already in cache TextLooks.noLooks => EXIT; -- this is an unused entry ENDCASE; SELECT (loc _ loc+1) FROM looksCacheSize => IF (loc _ 0)=initloc THEN ERROR; -- cache full initloc => ERROR; -- cache full ENDCASE; ENDLOOP; looksCacheResults[loc] _ ref^; lks[loc] _ looks; inputs[loc] _ input; looksCacheCount _ looksCacheCount+1 }; frame: Frame _ NIL; styleName: Name; IF looks = TextLooks.noLooks THEN RETURN; loc _ initloc _ Basics.BITXOR[LOOPHOLE[looks, TextLooks.LooksBytes].byte0, Basics.BITXOR[LOOPHOLE[looks, TextLooks.LooksBytes].byte1, Basics.BITXOR[LOOPHOLE[looks, TextLooks.LooksBytes].byte2, Hash[ref]]]] MOD looksCacheSize; IF FindInLooksCache[] THEN RETURN; frame _ GetFrame[ref, styleName _ ref.name[style], kind]; input _ ref^; -- save the input value of the record FOR c: CHARACTER IN TextLooks.Look DO IF looks[c] THEN [] _ ExecuteName[frame,lookNames[c]] ENDLOOP; FreeFrame[frame,styleName,kind]; frame _ NIL; PutInLooksCache[] }; looksCacheInfo: REF LooksCacheInfoRecord _ TextNode.pZone.NEW[LooksCacheInfoRecord]; LooksCacheInfoRecord: TYPE = RECORD [ looksCacheCount: CARDINAL _ 0, looksCacheLooks: REF LooksCacheLooks, looksCacheInputs: REF LooksCacheBodies, looksCacheResults: REF LooksCacheBodies, looksCacheProbes, looksCacheHits: INT _ 0 ]; looksCacheSize: CARDINAL = 16; -- should be a power of 2 looksCacheMax: CARDINAL = (looksCacheSize*4)/5; -- don't fill too full LooksCacheLooks: TYPE = ARRAY [0..looksCacheSize) OF TextLooks.Looks; LooksCacheBodies: TYPE = ARRAY [0..looksCacheSize) OF StyleBody; InitLooksCacheInfo: PROC = { OPEN looksCacheInfo; looksCacheLooks _ TextNode.pZone.NEW[LooksCacheLooks]; looksCacheInputs _ TextNode.pZone.NEW[LooksCacheBodies]; looksCacheResults _ TextNode.pZone.NEW[LooksCacheBodies]; }; lookNames: ARRAY TextLooks.Look OF Name; FlushLooksCache: ENTRY PROC [init: BOOLEAN _ FALSE] = { ENABLE UNWIND => NULL; ClearLooksCache[] }; ClearLooksCache: PROC [init: BOOLEAN _ FALSE] = { OPEN looksCacheInfo; IF ~init AND looksCacheCount = 0 THEN RETURN; looksCacheCount _ 0; FOR i: CARDINAL IN [0..looksCacheSize) DO looksCacheLooks[i] _ TextLooks.noLooks; ENDLOOP }; nullObject: NameSymbolTable.Object = NameSymbolTable.NullObject[]; ApplyObject: PUBLIC PROC [ref: Ref, object: NameSymbolTable.Object, kind: OfStyle _ screen] = { OPEN objectCacheInfo; objects: REF ObjectCacheObjects _ objectCacheObjects; inputs: REF ObjectCacheBodies _ objectCacheInputs; input: StyleBody; initloc, loc: CARDINAL; initDepth, finalDepth: CARDINAL; obj: Object _ LOOPHOLE[object]; FindInObjectCache: ENTRY PROC RETURNS [BOOLEAN] = { ENABLE UNWIND => NULL; objectCacheProbes _ objectCacheProbes+1; DO -- search cache SELECT objects[loc] FROM object => IF inputs[loc] = ref^ THEN { ref^ _ objectCacheResults[loc]; objectCacheHits _ objectCacheHits+1; RETURN [TRUE] }; nullObject => RETURN [FALSE]; -- this is an unused entry ENDCASE; SELECT (loc _ loc+1) FROM objectCacheSize => IF (loc _ 0)=initloc THEN RETURN [FALSE]; initloc => RETURN [FALSE]; ENDCASE; ENDLOOP }; PutInObjectCache: ENTRY PROC = { ENABLE UNWIND => NULL; IF objectCacheCount = objectCacheMax THEN ClearObjectCache[]; loc _ initloc; DO -- search cache for place to put the entry SELECT objects[loc] FROM object => IF inputs[loc] = input THEN RETURN; -- already in cache nullObject => EXIT; -- this is an unused entry ENDCASE; SELECT (loc _ loc+1) FROM objectCacheSize => IF (loc _ 0)=initloc THEN ERROR; -- cache full initloc => ERROR; -- cache full ENDCASE; ENDLOOP; objectCacheCount _ objectCacheCount+1; inputs[loc] _ input; objectCacheResults[loc] _ ref^; objects[loc] _ object }; HashObject: PROC RETURNS [CARDINAL] = { ob: RECORD [ a, b, c, d: CARDINAL ] _ LOOPHOLE[object]; RETURN [ob.b] }; frame: Frame _ NIL; styleName: Name; IF object = nullObject THEN RETURN; loc _ initloc _ Basics.BITXOR[HashObject[],Hash[ref]] MOD objectCacheSize; IF FindInObjectCache[] THEN RETURN; frame _ GetFrame[ref, styleName _ ref.name[style], kind]; input _ ref^; -- save the input value of the record TRUSTED {initDepth _ JaMOps.CountStack[frame.opstk]; JaMOps.Execute[frame,CVX[obj]]; IF (finalDepth _ JaMOps.CountStack[frame.opstk]) # initDepth THEN { PushObject[frame,obj]; PushText[frame,"Failed to leave stack at same depth after execution. "]; StyleError[frame,2] } ELSE PutInObjectCache[]}; FreeFrame[frame,styleName,kind]; frame _ NIL }; objectCacheInfo: REF ObjectCacheInfoRecord _ TextNode.pZone.NEW[ObjectCacheInfoRecord]; ObjectCacheInfoRecord: TYPE = RECORD [ objectCacheCount: CARDINAL, objectCacheObjects: REF ObjectCacheObjects, objectCacheInputs: REF ObjectCacheBodies, objectCacheResults: REF ObjectCacheBodies, objectCacheProbes, objectCacheHits: INT _ 0 ]; objectCacheSize: CARDINAL = 16; -- should be a power of 2 objectCacheMax: CARDINAL = (objectCacheSize*4)/5; -- don't fill too full ObjectCacheObjects: TYPE = ARRAY [0..objectCacheSize) OF NameSymbolTable.Object; ObjectCacheBodies: TYPE = ARRAY [0..objectCacheSize) OF StyleBody; InitObjectCacheInfo: PROC = { OPEN objectCacheInfo; objectCacheObjects _ TextNode.pZone.NEW[ObjectCacheObjects]; objectCacheInputs _ TextNode.pZone.NEW[ObjectCacheBodies]; objectCacheResults _ TextNode.pZone.NEW[ObjectCacheBodies] }; FlushObjectCache: ENTRY PROC [init: BOOLEAN _ FALSE] = { ENABLE UNWIND => NULL; ClearObjectCache[] }; ClearObjectCache: PROC [init: BOOLEAN _ FALSE] = { OPEN objectCacheInfo; IF ~init AND objectCacheCount = 0 THEN RETURN; objectCacheCount _ 0; FOR i: CARDINAL IN [0..objectCacheSize) DO objectCacheObjects[i] _ nullObject; ENDLOOP }; FlushCaches: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; ClearCaches[FALSE] }; ClearCaches: PROC [init: BOOLEAN] = { ClearApplyAllCache[init]; ClearRuleCache[init]; ClearLooksCache[init]; ClearObjectCache[init] }; <<-- ***** Execute>> executingName: PUBLIC Name; ExecuteName: PROC [frame: Frame, name: Name] RETURNS [ok: BOOLEAN] = TRUSTED { <<-- makes sure that same stack depth after execute>> initDepth: CARDINAL _ JaMOps.CountStack[frame.opstk]; finalDepth: CARDINAL; nameObj: Object; [ok,] _ JaMOps.TryToLoad[frame, nameObj _ NameToObject[name]]; IF ~ok THEN RETURN; executingName _ name; JaMOps.Execute[frame,CVX[nameObj]]; executingName _ NameSymbolTable.nullName; IF (finalDepth _ JaMOps.CountStack[frame.opstk]) # initDepth THEN { PushText[frame,"failed to leave stack at same depth after execution."]; PushName[frame,name]; StyleError[frame,2] }}; StyleError: PUBLIC PROC [frame: Frame, num: INTEGER] = TRUSTED { PushInteger[frame,num]; JaMOps.Execute[frame,CVX[NameToObject[styleerror]]] }; <<-- ***** Miscellaneous>> StyleNameForNode: PUBLIC PROC [node: TextNode.Ref] RETURNS [name: Name] = { <<-- does an ApplyAll and then returns the style name>> s: Ref _ Alloc[]; ApplyAll[s, node]; name _ GetStyleName[s]; Free[s] }; <<-- Initialization>> started: BOOLEAN _ FALSE; InitLookNames: PROC = TRUSTED { <<-- names are "look.a", "look.b", "look.c", etc.>> txt: STRING _ [6]; txt[0] _ 'l; txt[1] _ txt[2] _ 'o; txt[3] _ 'k; txt[4] _ '.; txt.length _ 6; FOR c: CHARACTER IN TextLooks.Look DO txt[5] _ c; lookNames[c] _ MakeName[LOOPHOLE[LONG[txt],REF READONLY TEXT]]; ENDLOOP}; StartApply: PUBLIC PROCEDURE = BEGIN changeSet: EditNotify.ChangeSet; frame: Frame _ NIL; IF started THEN RETURN; started _ TRUE; frame _ JaMOps.defaultFrame; changeSet[ChangingProp] _ TRUE; changeSet[ChangingType] _ TRUE; changeSet[MovingNodes] _ TRUE; changeSet[NodeNesting] _ TRUE; changeSet[InsertingNode] _ TRUE; EditNotify.AddNotifyProc[Notify,after,high,changeSet]; defaultStyle _ Create[]; defaultName _ MakeName["default"]; rootName _ MakeName["root"]; InitLookNames[]; InitApplyCacheRecord[]; InitRuleCacheInfo[]; InitLooksCacheInfo[]; InitObjectCacheInfo[]; ClearCaches[TRUE]; SetDefaultStyle["Cedar"]; END; StartApply; END.