/** * This module contains strategies for operating on hashtables. * * It is important to note that hashtables do not have value semantics; * all operations on hashtables will modify the existing hashtable. * Therefore, when you pass a hashtable to a strategy, it will modify * the hashtable by side-effect. * * Hash tables support constant time storing and retrieving of * values associated with keys. This module wraps the hashtable * facility provided by the ATerm library. * * @author Eelco Visser <visser@acm.org> * @author Martin Bravenboer <martin.bravenboer@gmail.com> * @author Karl Trygve Kalleberg <karltk@strategoxt.org> - some documentation * */ module collection/hash-table/common imports collection/list/- signature constructors /** * Represents a reference to a hashtable. */ Hashtable : ImplDep -> Hashtable /** * Hashtable construction and deconstruction */ strategies /** * Create a new hashtable with default size and load. * * @type _ -> HashTable(K, V) */ new-hashtable = new-hashtable(|117,75) /** * Create a new hashtable with specified size and load. * * @param initial_size - the initial capacity of the hashtable * @param max_load - the load percentage at which the hashtable is expanded * @type _ -> HashTable(K, V) */ new-hashtable(|initial_size, max_load) = prim("SSL_hashtable_create", initial_size, max_load); !Hashtable(<id>) /** * Destroys and deallocates memory consumed by an existing hashtable. * * @type HashTable(K, V) -> HashTable(K, V) */ hashtable-destroy = ?Hashtable(tbl); prim("SSL_hashtable_destroy", tbl); !Hashtable(<id>) /** * Clears the contents of an existing hashtable. * * @type HashTable(K, V) -> HashTable(K, V) */ hashtable-clear = ?Hashtable(tbl); prim("SSL_hashtable_reset", tbl); !Hashtable(<id>) /** * Hashtable operations */ strategies /** * Puts a new key, value pair into the hash table. If the key is * already bound to a value, the old value will be replaced. * * @param key - any term can be used as a key * @param value - any term can be used as a value * @type HashTable(K, V) -> HashTable(K, V) */ hashtable-put(|key, value) = ?Hashtable(tbl); prim("SSL_hashtable_put", tbl, key, value); !Hashtable(<id>) /** * Gets the value for a key. If the key does not exist, this strategy * fails. * * @param key * @type HashTable(K, V) -> V */ hashtable-get(|key) = ?Hashtable(tbl); prim("SSL_hashtable_get", tbl, key) /** * Removes a key (with its associated value) from the hashtable. This * strategy will succeed even if the key does not exist. * * @type HashTable(K, V) -> HashTable(K, V) */ hashtable-remove(|key) = ?Hashtable(tbl); prim("SSL_hashtable_remove", tbl, key); !Hashtable(<id>) /** * Get a list containing all keys of a hashtable. * * @type HashTable(K, V) -> List(K) */ hashtable-keys = ?Hashtable(tbl); prim("SSL_hashtable_keys", tbl) /** * Get a list containing all values of a hashtable * * @type HashTable(K, V) -> List(V) */ hashtable-values = ?htbl ; hashtable-keys ; map({k: ?k; <hashtable-get(|k)> htbl}) /** * Apply an iterative fold left (starting with first element) over * the keys of a hash table; useful for applying an operation to each * key without constructing a list of keys. * * @param (k, v) * a -> a * @param a * @type HashTable(k, v) -> a */ hashtable-fold(s : k * v * a -> a | t) = ?Hashtable(tbl) ; prim("SSL_table_fold", s | t, tbl) /** * Apply an iterative fold left (starting with first element) over * the keys of a hash table; useful for applying an operation to each * key without constructing a list of keys. * * @param k * a -> a * @param a * @type HashTable(k, v) -> a */ hashtable-fold-keys(s : k * a -> a | t) = ?Hashtable(tbl) ; prim("SSL_table_keys_fold", s | t, tbl) /** * Apply an iterative fold left (starting with first element) over * the values of a hash table; useful for applying an operation to each * value without constructing a list of values. * * @param v * a -> a * @param a * @type (v * a -> a) * a * HashTable(k, v) -> a */ hashtable-fold-values(s : v * a -> a | t) = ?Hashtable(tbl) ; prim("SSL_table_values_fold", s | t, tbl) /** * Hashtable bulk operations */ strategies /** * Get the contents of a hashtable as a list of key-value tuples. * * @type HashTable(k,v) -> List((k,v), ...) */ hashtable-getlist = ?htbl ; let insert(|k, v) = ![(k, v) | <id>] in hashtable-fold(insert | []) end /** * Put a list of key,value tuples into a hashtable. * * @param kv List(Tuple(K, V), ...) * @type HashTable(K, V) -> HashTable(K, V) */ hashtable-putlist(|kv*) = ?htbl ; where(<map(\ (key, value) -> <hashtable-put(|key, value)> htbl \)> kv*) hashtable-putlist(f : ATerm * ATerm * Hashtable -> Hashtable|kv*) = ?htbl ; where(<map(\ (key, value) -> <f(|key, value)> htbl \)> kv*) /** * Create a new hashtable and initialize it from a key-value pair list. * * @type List(Tuple(K, V), ...) -> HashTable(K, V) */ hashtable-init = ?kv* ; new-hashtable(|<length>, 75) ; hashtable-putlist(|kv*) /** * Create a new hashtable and copies the entire contents of the current * table into the new one. * * @type HashTable(K, V) -> HashTable(K,V) */ hashtable-copy = hashtable-getlist ; hashtable-init /** * Stack operations (when stored values in hashtables are list-based stacks) */ strategies /** * Pushes value on top of the stack for key. * Current term is the hashtable and remains untouched. * * @param key t - the key used for lookup * @param value a - the value to insert * @type HashTable(K, V) -> HashTable(K, V) */ hashtable-push(|key, value) = hashtable-put(|key, [value | <hashtable-get(|key) <+ ![]>]) /** * Pushes values on top of the stack for key. * Current term is the hashtable and remains untouched. * * @param key t - the key used for lookup * @param value List(a) - the values to insert * @type HashTable(K, V) -> HashTable(K, V') */ hashtable-pushlist(|key, value) = hashtable-put(|key, <conc>(value, <hashtable-get(|key) <+ ![]>)) /** * Pushes union of values and already present values * Current term is the hashtable. * * @param key t - the key used for lookup * @param value List(a) - the values to insert * @type HashTable(K, V) -> HashTable(K, V') */ hashtable-pushunion(|key, value) = hashtable-put(|key, <union>(value, <hashtable-get(|key) <+ ![]>)) /** * Pops off the top element of the stack for key. * Current term is the hashtable and remains untouched. * * @param key t - the key used for lookup * @type HashTable(K, (V, ...)) -> V */ hashtable-pop(|key) = hashtable-put(|key, <hashtable-get(|key); Tl>) /** * Gets the top value from the stack for key (the stack remains untouched) * Current term is the hashtable, output is the top value. * * @param key t - the key used for lookup * @type HashTable(K, (V, ...)) -> V */ hashtable-peek(|key) = hashtable-get(|key); Hd /** * Gets the top value from the stack for key and pops it off the stack. * Current term is the hashtable, output is the top value. * * @param key t - the key used for lookup * @type HashTable(K, (V, ...)) -> V */ hashtable-peekpop(|key) = where(hashtable-peek(|key) => val) ; hashtable-pop(|key) ; !val /** * Mnemonic parameter strategies for hashtable-merge. */ strategies symmetric-yes = id symmetric-no = fail iterate1-yes = id iterate1-no = fail iterate2-yes = id iterate2-no = fail remove-yes = id remove-no = fail default-no = fail change1-yes = id change1-no = fail change2-yes = id change2-no = fail strategies /** * Merge two hashtables changing one or both. * * This a highly generic strategy for merging two hashtables. The algorithm * is parameterized with a number of knobs that determine its behaviour. * * (A little language for table merging!-) * * @param symm : apply changes to both tables, if not apply to tbl1. * @param iterate1 : iterate over keys of tbl1 * @param iterate2 : iterate over keys of tbl2 * @param remove : remove table entry if undefined in other table * @param merge : operation on pair of values * @param default : default value in case lookup in other table fails * @param lookup1 : given a key produce value for left table * @param lookup2 : given a key produce value for right table * * @todo : with a proper partial evaluator for Stratego this strategy can be * used to create appropriately specialized algorithms. */ hashtable-merge(symm, iterate1, iterate2, remove, merge , default, change1, change2, lookup1, lookup2) : (tbl1, tbl2) -> tbl1 where if iterate1 then <hashtable-keys> tbl1 ; filter({key, val1, val2, val3: ?key ; if <lookup2> key => val2 then <hashtable-get(|key)> tbl1 => val1 ; <merge>(val1, val2) => val3 ; ior( if <?val1> val3 then fail // no change to tbl1 else <hashtable-put(|key, val3)> tbl1; change1 end , if symm; <?val2> val3 then fail // no change to tbl2 else <hashtable-put(|key, val3)> tbl2; change2 end ) else if <default> key => val2 then <hashtable-get(|key)> tbl1 => val1 ; <merge>(val1, val2) => val3 ; ior( if <?val1> val3 then fail // no change to tbl1 else <hashtable-put(|key, val3)> tbl1; change1 end , if symm then <hashtable-put(|key, val3)> tbl2; change2 end ) else // lookup2 failed, no default if remove then <hashtable-remove(|key)> tbl1; if symm then <hashtable-remove(|key)> tbl2; change2 end else fail // no change to either table end end end }) else ![] end => changes1 ; if iterate2 then <hashtable-keys> tbl2 ; filter({key, val1, val2, val3: ?key ; if iterate1 then not(<hashtable-get(|key)>tbl1) end ; if <lookup1> key => val1 then <hashtable-get(|key)> tbl2 => val2 ; <merge>(val1, val2) => val3 ; ior(if <?val1> val3 then fail // no change to tbl1 else <hashtable-put(|key, val3)> tbl1; change1 end ,if symm; <?val2> val3 then fail // no change to tbl2 else <hashtable-put(|key, val3)> tbl2; change2 end ) else if <default> key => val1 then <hashtable-get(|key)> tbl2 => val2 ; <merge>(val1, val2) => val3 ; <hashtable-put(|key, val3)> tbl1 ; if symm; <not(?val2)> val3 then <hashtable-put(|key, val3)> tbl2 end else // lookup1 failed and no default if remove then <hashtable-remove(|key)> tbl1; if symm then <hashtable-remove(|key)> tbl2; change2 end else fail // no change to either table end end end }) else ![] end => changes2 ; (<not([])> changes1 <+ <not([])> changes2) /** * Version without change arguments for backward compatibility */ hashtable-merge(symm, iterate1, iterate2, remove, merge, default, lookup1, lookup2) = hashtable-merge(symm, iterate1, iterate2, remove, merge, default, change1-yes, change2-yes, lookup1, lookup2) /** * Merge hashtables creating a new one. */ hashtable-merge-func(iterate1, iterate2, remove, merge, default, lookup1, lookup2) : (tbl1, tbl2) -> tbl3 where <hashtable-copy> tbl1 => tbl3 ; <hashtable-merge(symmetric-no, iterate1, iterate2, remove, merge, default, lookup1, lookup2)> (tbl3, tbl2) strategies // intersection merges /** * Intersect hashtables. * * Changes tbl1; fails if no changes are made * Assumes that both hashtables contains lists as values, and intersects * the two listvalues for each key. * This is a 'pure' intersection: only keys that appear in both tables * remain in the input table, rest is removed. */ hashtable-intersect(|tbl2) = ?tbl1 ; !(tbl1, tbl2) ; hashtable-merge( symmetric-no, iterate1-yes, iterate2-no, remove-yes, isect, default-no, \ key -> <hashtable-get(|key)> tbl1 \, \ key -> <hashtable-get(|key)> tbl2 \ ) /** * Intersect hashtables with default element. * * Changes tbl1; fails if no changes are made * Assumes that both hashtables contains lists as values, and intersects * the two listvalues for each key. * Places a dummy value if key does not exist in one of the tables. */ hashtable-intersect-wempty(|tbl2, emptyElt) = ?tbl1 ; !(tbl1, tbl2) ; hashtable-merge( symmetric-no, iterate1-yes, iterate2-yes, remove-no, isect, !emptyElt, \ key -> <hashtable-get(|key)> tbl1 \, \ key -> <hashtable-get(|key)> tbl2 \ ) /** * Intersect hashtables symmetrically with default element. * * Changes tbl1 and tbl2; fails if no changes are made in tbl1 * Assumes that both hashtables contains lists as values, and intersects * the two listvalues for each key. * Places a dummy value if key does not exist in one of the tables. */ hashtable-intersect-symm-wempty(|emptyElt) = ?(tbl1, tbl2) ; hashtable-merge( symmetric-yes, iterate1-yes, iterate2-yes, remove-no, isect, !emptyElt, \ key -> <hashtable-get(|key)> tbl1 \, \ key -> <hashtable-get(|key)> tbl2 \ ) /** * Intersect hashtables symmetrically with default element. * * Changes tbl1 and tbl2; fails if no changes are made in tbl1 * Assumes that both hashtables contains lists as values, and intersects * the two listvalues for each key. * Places a dummy value if key does not exist in one of the tables. */ hashtable-intersect-symm-wempty-ignore-left(|emptyElt) = ?(tbl1, tbl2) ; hashtable-merge( symmetric-yes, iterate1-yes, iterate2-yes, remove-no, isect, !emptyElt, change1-no, change2-yes, \ key -> <hashtable-get(|key)> tbl1 \, \ key -> <hashtable-get(|key)> tbl2 \ ) strategies // union merges /** * Union of hashtables * * Changes tbl1; fails if no changes are made */ hashtable-union(|tbl2) = ?tbl1 ; !(tbl1, tbl2) ; hashtable-merge( symmetric-no, iterate1-no, iterate2-yes, remove-no, union, ![], \ key -> <hashtable-get(|key)> tbl1 \, fail ) hashtable-union-wempty(|tbl2, emptyElt) = ?tbl1 ; !(tbl1, tbl2) ; hashtable-merge( symmetric-no, iterate1-no, iterate2-yes, remove-no, union, !emptyElt, \ key -> <hashtable-get(|key)> tbl1 \, fail ) hashtable-union-symm-wempty(|emptyElt) = ?(tbl1, tbl2) ; hashtable-merge( symmetric-yes, iterate1-yes, iterate2-yes, remove-no, union, !emptyElt, \ key -> <hashtable-get(|key)> tbl1 \, \ key -> <hashtable-get(|key)> tbl2 \ ) strategies /** * Checks whether two hashtables have equal contents. * This strategy uses the basic `eq` to compare the elements. * * @type Tuple(HashTable(K, V), HashTable(K,V)) -> _ */ hashtable-eq = hashtable-eq(eq) /** * Checks whether two tables have the same keys, and for each key whether * the associated values are the same (order of keys thus doesn't matter) * * @param test strategy that will compare two elements upon their equality. * @inc hashtable-eq * * @type Tuple(HashTable(K, V), HashTable(K,V)) -> _ */ hashtable-eq(eq) = ?(tbl1, tbl2); where((hashtable-keys => k*, hashtable-keys); set-eq) ; where(<map( \ key -> <eq> (<hashtable-get(|key)> tbl1, <hashtable-get(|key)> tbl2) \ )> k*) /** * Legacy table strategies */ strategies /** * Retrieves the primitive table-table as a Hashtable * */ table-hashtable = prim("SSL_table_hashtable"); !Hashtable(<id>) /** * Retrieves a named table by looking it up in the table-table. */ lookup-table(|name) = table-hashtable ; ( hashtable-get(|name) <+ where(<table-create> name); hashtable-get(|name)) /** * Creates a table with a given name, which can be any term. */ table-create = ?name ; where(new-hashtable => htbl) ; where(table-hashtable; hashtable-put(|name, htbl)) /** * Destroy a table with a given name, which can be any term. */ table-destroy = ?name ; where(table-hashtable ; where(lookup-table(|name) ; hashtable-destroy) ; hashtable-remove(|name) ) /** * Associate a value with a key in a table. * * E.g. <table-put> (t1, key, value) associates value with key in the t1 table. */ table-put = where( ?(name,key,value) ; lookup-table(|name) ; hashtable-put(|key, value) ) /** * Get the value associated with a key from a table. * * E.g. <table-get> (t1, key) yields the value associated to key or fails in the t1 table. */ table-get = ?(name,key) ; lookup-table(|name) ; hashtable-get(|key) /** * Remove association of a key in a table. * * E.g. <table-remove> (t1, key) removes the entry for key from table t1. */ table-remove = where( ?(name,key) ; lookup-table(|name) ; hashtable-remove(|key) ) /** * Produce a list of keys of a table. * * E.g. <table-keys>t1 produces the list of keys of table t1. */ table-keys = ?name ; lookup-table(|name) ; hashtable-keys /** * Renames a table, i.e. only the entry in the table-table is renamed, * the table contents remain untouched. */ table-rename = ?(from-name, to-name) ; where(lookup-table(|from-name) => htbl) ; table-hashtable ; hashtable-put(|to-name, htbl) ; hashtable-remove(|from-name) ; !to-name strategies /** * Get a list of key-value pairs. * * E.g. <table-getlist>t1 produces the list of (key,value) pairs in the table t1. */ table-getlist = ?name; table-keys; map(\ x -> (x, <table-get> (name, x))\ ) /** * Put a list of key-value pairs in a table. * * E.g. <table-putlist>(name, list) puts the (key,value) pairs in list into table t1. */ table-putlist = ?(name, list); <map({x,y: ?(x, y); <table-put> (name, x, y)})> list table-putlist(s) = ?(name, list); <map({x,y: ?(x, y); <s> (name, x, y)})> list table-copy = ?(name1, name2); where(<table-putlist>(name2, <table-getlist> name1)) /* Symbol tables that deal with scoped bindings need to be able to store multiple bindings for the same symbol. The following table operations can be used to maintain a stack (list) of values for each key. */ strategies table-push = ?(dis, key, val); where(<table-put>(dis, key, [val | <table-get <+ ![]>(dis, key)])) table-pop = ?(dis, key); where(<table-put>(dis, key, <table-get; Tl <+ ![]>(dis, key))) table-pop-rm = ?(dis, key); where( <table-get>(dis, key) => [_|xs] < <table-put>(dis, key, xs) + <table-remove>(dis, key) ) table-pop-get = ?(dis, key); where( <table-get>(dis, key) => [val | vals] ; <table-put>(dis, key, vals) ); !val table-lookup = ?(dis, key); <table-get>(dis, key); Hd table-replace = ?(dis, key, value); where(<table-get>(dis, key) => [_|values] ; <table-put>(dis, key, [value|values]) ) table-union = ?(table, key, val); where(<table-put> (table, key, <union> (val, <table-get <+ ![]> (table, key)))) table-diff = ?(table, key, val); where(<table-put> (table, key, <diff> (<table-get <+ ![]> (table, key), val))) table-append = ?(table, key, val); where(<table-put> (table, key, <union> (<table-get <+ ![]> (table, key), val)))