-- JaMDictImpl.mesa -- Original version by Martin Newell, January 1979 -- Last changed by Bill Paxton, February 24, 1981 10:37 AM -- Last changed by Doug Wyatt, 21-Oct-81 17:21:22 -- Administers dictionaries in VM -- A dictionary is a type of Object, however additional information is held -- in a DictDesc, which also lives in VM -- A Dictionary is a set of tuples, in which both key and value -- are Objects (key is not necessarily a string) -- Present implementation: -- Dictionary is allocated as a contiguous vector of tuples -- Hash coding is used to access tuples -- Dictionary stack is cached with single level cache. DIRECTORY JaMBasic USING [Object, Tuple, vmNIL], JaMDict USING [DD, freeHash, Hash, Loc, locNIL, Slot, slotNIL], JaMInternal USING [Cache, CacheRecord, Frame, HashSequence, TupleSequence], JaMOps USING [AGet, APut, Assert, Bug, Compare, Empty, Equal, Error, ForEachFrame, ForEachFrameExcept, Full, Install, InstallReason, KeyName, limitchk, MakeName, MarkLoop, nullOb, Pop, PopCardinal, PopDict, Push, PushBoolean, PushCardinal, PushInteger, RegisterExplicit, RegisterInternal, StackForAll, TopDict, typechk, UnmarkLoop], JaMStorage USING [Zone], JaMVM USING [AllocArray, AllocDict, GetDict, GetTuple, PutDict, PutTuple]; JaMDictImpl: PROGRAM IMPORTS JaMDict, JaMOps, JaMStorage, JaMVM EXPORTS JaMDict, JaMOps = { OPEN VM:JaMVM, JaMOps, JaMDict, JaMInternal, JaMBasic; -- Types and constants nullTuple: Tuple = [nullOb,nullOb]; nullDict: dict Object = [L,dict[vmNIL]]; maxlenLimit: CARDINAL = 20000; -- maximum maxlen for a dictionary maxattLimit: CARDINAL = 1000; -- maximum number of attached dictionaries initCacheSize: CARDINAL = 67; -- GLOBALS zone: UNCOUNTED ZONE = JaMStorage.Zone[]; undefkey,dictfull,attachmentcycle,notattached,dictundflw,dictovrflw: name Object; dfacmd,afacmd: command Object; -- Low level dictionary operations AGetDict: PROC[array: array Object, i: CARDINAL] RETURNS[dict Object] = { ob: Object _ AGet[array,i]; WITH ob:ob SELECT FROM dict => RETURN[ob]; ENDCASE => ERROR Bug; }; LookUpDict: PUBLIC PROC[dd: POINTER TO READONLY DD, hash: CARDINAL, key: Object] RETURNS[BOOLEAN,Slot] = { -- Looks in dictionary for object with key, hashed to hash. -- If found then returns [TRUE, where it is] -- else returns [FALSE, where it would go or slotNIL if no room] slot: Slot _ dd.beg + LONG[hash MOD dd.size]*SIZE[Tuple]; IF key.type = null THEN ERROR Error[typechk]; -- better not allow this for a key THROUGH[0..dd.size) DO tuple: Tuple _ VM.GetTuple[slot]; IF Equal[tuple.key,key] THEN RETURN[TRUE,slot]; -- found a match IF tuple.key.type = null THEN -- found an empty slot RETURN[FALSE,IF dd.curlen ERROR Bug; -- no empty slots ENDLOOP; dd.curlen _ dd.curlen - 1; VM.PutDict[dict,dd]; RETURN[TRUE]; }; ClearDict: PROC[dict: dict Object] = { dd: DD _ VM.GetDict[dict]; slot: Slot _ dd.beg; THROUGH[0..dd.size) DO VM.PutTuple[slot,nullTuple]; slot _ slot + SIZE[Tuple]; ENDLOOP; Assert[slot=dd.end]; dd.curlen _ 0; VM.PutDict[dict,dd]; }; -- Cache operations LookUpCache: PROC[cache: Cache, hash: CARDINAL, key: Object] RETURNS[BOOLEAN,Loc] = { -- Looks in dictionary cache for object with key, hashed to hash. -- If found then returns [TRUE, where it is] -- else returns [FALSE, where it would go or locNIL if no room] loc: Loc _ hash MOD cache.size; cache.probes _ cache.probes + 1; THROUGH[0..cache.size) DO h: CARDINAL _ cache.hash[loc]; IF h=hash THEN { k: Object _ cache.table[loc].key; -- compare keys IF Equal[k,key] THEN { cache.hits _ cache.hits + 1; RETURN[TRUE,loc] }; } ELSE IF h=freeHash THEN RETURN[FALSE,IF cache.curlen [known, slot, dict] _ FindInDict[ob, hash, name]; ENDCASE => ERROR Error[typechk]; RETURN[known] }; IF StackForAll[frame.dictstk, Find] THEN { EnterInCache[frame.cache,hash,VM.GetTuple[slot]]; -- put in cache, anticipating a Get RETURN[TRUE,dict] } ELSE RETURN[FALSE,nullDict]; }; Get: PUBLIC PROC[dict: dict Object, key: Object] RETURNS[Object] = { -- Generates undefkey: known: BOOLEAN; value: Object; [known,value] _ TryToGet[dict,key]; IF known THEN RETURN[value] ELSE ERROR Error[undefkey]; }; TryToGet: PUBLIC PROC[dict: dict Object, key: Object] RETURNS[BOOLEAN,Object] = { name: Object _ KeyName[key]; hash: CARDINAL _ Hash[name]; dd: DD _ VM.GetDict[dict]; known: BOOLEAN; slot: Slot; [known,slot] _ LookUpDict[@dd,hash,name]; IF known THEN RETURN[TRUE,VM.GetTuple[slot].value] ELSE RETURN[FALSE,nullOb]; }; Put: PUBLIC PROC[dict: dict Object, key, value: Object] = { -- Generates dictfull: tuple: Tuple _ [KeyName[key],value]; hash: CARDINAL _ Hash[tuple.key]; Delete: PROC[f: Frame] = { DeleteInCache[f.cache,hash,tuple.key] }; EnterInDict[dict,hash,tuple]; ForEachFrame[Delete]; -- in case dict is on stack }; Def: PUBLIC PROC[frame: Frame, key, value: Object] = { -- Enters into dictionary on top of dictstk, -- Generates dictfull: dict: dict Object = TopDict[frame.dictstk]; tuple: Tuple _ [KeyName[key],value]; hash: CARDINAL _ Hash[tuple.key]; Delete: PROC[f: Frame] = { DeleteInCache[f.cache,hash,tuple.key] }; EnterInDict[dict,hash,tuple]; EnterInCache[frame.cache,hash,tuple]; -- insert in this frame's cache ForEachFrameExcept[frame,Delete]; -- in case dict is on stack }; Load: PUBLIC PROC[frame: Frame, key: Object] RETURNS[Object] = { known: BOOLEAN; value: Object; [known,value] _ TryToLoad[frame,key]; IF known THEN RETURN[value] ELSE ERROR Error[undefkey]; }; TryToLoad: PUBLIC PROC[frame: Frame, key: Object] RETURNS[BOOLEAN,Object] = { -- Return value looked up in highest dictionary having key in dictstk, -- searching attached dictionaries if necessary cache: Cache _ frame.cache; name: Object _ KeyName[key]; hash: CARDINAL _ Hash[name]; known: BOOLEAN; loc: Loc; dict: dict Object; slot: Slot; Find: PROC[ob: Object] RETURNS[BOOLEAN] = { WITH ob:ob SELECT FROM dict => [known,slot,dict] _ FindInDict[ob,hash,name]; ENDCASE => ERROR Error[typechk]; RETURN[known] }; [known,loc] _ LookUpCache[cache,hash,name]; -- try the cache first IF known THEN RETURN[TRUE,cache.table[loc].value]; -- found it! (this should be common) -- Not in cache, must search dictionary stack known _ StackForAll[frame.dictstk, Find]; IF known THEN { tuple: Tuple _ VM.GetTuple[slot]; InsertInCache[cache,hash,tuple,loc]; RETURN[TRUE,tuple.value] } ELSE RETURN[FALSE,nullOb]; }; Store: PUBLIC PROC[frame: Frame, key, value: Object] = { -- Enters into highest dictionary having key in dictstk, -- or into dict on top of dictstk if key not found. -- Generates dictfull: tuple: Tuple _ [KeyName[key],value]; hash: CARDINAL _ Hash[tuple.key]; known: BOOLEAN; dict: dict Object; slot: Slot; Find: PROC[ob: Object] RETURNS[BOOLEAN] = { WITH ob:ob SELECT FROM dict => [known,slot,dict] _ FindInDict[ob,hash,tuple.key]; ENDCASE => ERROR Error[typechk]; RETURN[known] }; IF StackForAll[frame.dictstk, Find] THEN VM.PutTuple[slot,tuple] ELSE EnterInDict[TopDict[frame.dictstk],hash,tuple]; EnterInCache[frame.cache,hash,tuple]; -- update cache }; Del: PUBLIC PROC[dict: dict Object, key: Object] = { -- Deletes object key from dictionary -- Generates undefkey if object not found: name: Object _ KeyName[key]; hash: CARDINAL _ Hash[name]; Delete: PROC[f: Frame] = { DeleteInCache[f.cache,hash,name] }; IF NOT DeleteInDict[dict,hash,name] THEN ERROR Error[undefkey]; ForEachFrame[Delete]; -- in case dict is on stack }; ClrDict: PUBLIC PROC[dict: dict Object] = { -- Deletes all objects from dictionary dict ClearDict[dict]; ClearAllCaches[]; -- in case dict is on stack }; ClearAllCaches: PROC = { Clear: PROC[f: Frame] = { ClearCache[f.cache] }; ForEachFrame[Clear]; -- in case dict is on stack }; AttachDict: PUBLIC PROC[dict, adict: dict Object] = { -- attach adict to dict -- Generates attachmentcycle IF AttachmentPath[adict,dict] THEN ERROR Error[attachmentcycle] ELSE { oldatt,newatt: array Object; dd: DD _ VM.GetDict[dict]; oldatt _ dd.attach; FOR i: CARDINAL IN[0..dd.curatt) DO IF AGetDict[oldatt,i] = adict THEN RETURN; -- already attached ENDLOOP; IF dd.curatt into dictionary -- Returns opstk: () -- No indication of whether an object with that key already existed. value: Object _ Pop[frame.opstk]; key: Object _ Pop[frame.opstk]; dict: dict Object _ PopDict[frame.opstk]; Put[dict, key, value]; }; JDef: PUBLIC PROC[frame: Frame] = { -- Expects opstk: (key, value), dictstk: (dictionary) -- Enters into dictionary -- Returns opstk: (), dictstk: (dictionary) -- No indication of whether an object with that key already existed. value: Object _ Pop[frame.opstk]; key: Object _ Pop[frame.opstk]; Def[frame, key, value]; }; JLoad: PUBLIC PROC[frame: Frame] = { -- Expects opstk: (key), dictstk: a dictionary stack -- Returns opstk: (value looked up in highest dictionary having key in dictstk), -- dictstk: unchanged key: Object _ Pop[frame.opstk]; value: Object _ Load[frame, key]; Push[frame.opstk,value]; }; JStore: PUBLIC PROC[frame: Frame] = { -- Expects opstk: (key, value), dictstk: a dictionary stack -- Enters into highest dictionary having key in dictstk, -- or into dict on top of dictstk -- Returns opstk: (), dictstk: unchanged value: Object _ Pop[frame.opstk]; key: Object _ Pop[frame.opstk]; Store[frame, key, value]; }; JDel: PUBLIC PROC[frame: Frame] = { -- Expects opstk: (dictionary, key) -- Deletes object key from dictionary -- Returns opstk: () key: Object _ Pop[frame.opstk]; dict: dict Object _ PopDict[frame.opstk]; Del[dict, key]; }; JClrDict: PUBLIC PROC[frame: Frame] = { -- Expects opstk: (dictionary) -- Deletes all objects from dictionary -- Returns opstk: () dict: dict Object _ PopDict[frame.opstk]; ClrDict[dict]; }; JBegin: PUBLIC PROC[frame: Frame] = { -- Expects opstk: (dictionary), dictstk: () -- Returns opstk: (), dictstk: (dictionary) dict: dict Object _ PopDict[frame.opstk]; Begin[frame,dict]; }; JEnd: PUBLIC PROC[frame: Frame] = { -- Expects dictstk: (dictionary) -- Returns dictstk: () End[frame]; }; NextTuple: PROC[dict: dict Object, old: Loc] RETURNS[key, value: Object, new: Loc] = { -- Increments old to location of next non-null tuple and returns new location -- together with the key and object of the tuple -- Initial call should have old=locNIL -- Returns new=locNIL if no more tuples -- Normal enumeration is not used because of way JaM control works dd: DD _ VM.GetDict[dict]; slot: Slot; IF old=locNIL THEN { new _ 0; slot _ dd.beg } ELSE { new _ old + 1; slot _ dd.beg + LONG[new]*SIZE[Tuple] }; WHILE new { -- Dictionary commands undefkey _ MakeName[".undefkey"L]; dictfull _ MakeName[".dictfull"L]; attachmentcycle _ MakeName[".attachmentcycle"L]; notattached _ MakeName[".notattached"L]; dictundflw _ MakeName[".dictundflw"L]; dictovrflw _ MakeName[".dictovrflw"L]; dfacmd _ RegisterInternal["@dictforall"L, DFAProc]; afacmd _ RegisterInternal["@attachedforall"L, AFAProc]; RegisterExplicit[frame, ".dict"L, JDict]; RegisterExplicit[frame, ".maxlength"L, JMaxLength]; RegisterExplicit[frame, ".known"L, JKnown]; RegisterExplicit[frame, ".where"L, JWhere]; RegisterExplicit[frame, ".get"L, JGet]; RegisterExplicit[frame, ".put"L, JPut]; RegisterExplicit[frame, ".def"L, JDef]; RegisterExplicit[frame, ".load"L, JLoad]; RegisterExplicit[frame, ".store"L, JStore]; RegisterExplicit[frame, ".del"L, JDel]; RegisterExplicit[frame, ".clrdict"L, JClrDict]; RegisterExplicit[frame, ".begin"L, JBegin]; RegisterExplicit[frame, ".end"L, JEnd]; RegisterExplicit[frame, ".dictforall"L, JDictForall]; RegisterExplicit[frame, ".curdict"L, JCurDict]; RegisterExplicit[frame, ".attachdict"L, JAttachDict]; RegisterExplicit[frame, ".detachdict"L, JDetachDict]; RegisterExplicit[frame, ".attachedforall"L, JAttachedForall]; RegisterExplicit[frame, ".detachall"L, JDetachAll]; RegisterExplicit[frame, ".clrdictcache"L, JClrDictCache]; RegisterExplicit[frame, ".dictcachestats"L, JDictCacheStats]; RegisterExplicit[frame, ".resetdictcachestats"L, JResetDictCacheStats]; }; ENDCASE; }; Install[InstallDict]; }. Paxton December 12, 1980 5:25 PM added attached dictionaries Paxton December 14, 1980 10:31 AM added CacheHash array and automatic growth for full dictionaries Paxton January 8, 1981 9:34 AM add MakeName and StringForName; change Hash and Match. Wyatt 16-Apr-81 0:16:54 Pilot conversion use String instead of StringDefs Wyatt 7-May-81 11:34:07 use hash field for NameStrings Wyatt 29-Aug-81 22:32:37 rewrite