/**
* 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@cs.uu.nl> - 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)))