b6d06e2424fab5e718bed1f073c8470c7bf40c5f
[feisty_meow.git] / huffware / huffotronic_scripts / set_comparator_library_v4.1.txt
1 
2 // huffware script: set comparator, by fred huffhines.
3 //
4 // provides a library of functions for managing sets.
5 //
6 // this script is licensed by the GPL v3 which is documented at: http://www.gnu.org/licenses/gpl.html
7 // do not use it in objects without fully realizing you are implicitly accepting that license.
8 //
9
10 // API for set operations.
11 //////////////
12 integer SET_COMPARATOR_HUFFWARE_ID = 10020;
13     // a unique ID within the huffware system for this script.
14 string HUFFWARE_PARM_SEPARATOR = "{~~~}";
15     // this pattern is an uncommon thing to see in text, so we use it to separate
16     // our commands in link messages.
17 string HUFFWARE_ITEM_SEPARATOR = "{|||}";
18     // used to separate lists of items from each other when stored inside a parameter.
19     // this allows lists to be passed as single string parameters if needed.
20 integer REPLY_DISTANCE = 100008;  // offset added to service's huffware id in reply IDs.
21 //////////////
22 string DEFINE_SET_CMD = "#def-set";
23     // adds a new set or replaces existing one with same name.  first parm is name of set,
24     // second and further parms are a list of elements that should be in the set.  return
25     // value is a boolean for success.
26 string REMOVE_SET_CMD = "#rm-set";
27     // trashes a named set.  first parm is the name.  returns a bool for success.
28 string ADD_ELEMENTS_CMD = "#add-elem";
29     // adds more elements to an existing set.  first parm is the name, second and more are
30     // the list of new elements.  set must already exist.  returns a bool for success.
31 string CUT_ELEMENTS_CMD = "#cut-elem";
32     // removes a set of elements from an existing set.  first parm is set name, second etc
33     // is list of elements to remove.  set must already exist.  returns bool.
34 string INTERSECT_CMD = "#inter-set";
35     // reports the set of elements in the intersection of two sets.  first and second parm
36     // are the set names.  returns a wrapped list of elements that are common members of both sets.
37 string UNION_CMD = "#union-set";
38     // returns the union of two named sets.  results sent similar to intersection.
39 string DIFFERENCE_CMD = "#diff-set";
40     // returns the difference of set A (parm 1) minus set B (parm 2).  results are similar
41     // to intersection.
42 string WHAT_MU_CMD = "#mu-set";
43     // returns one of the possibility values below to describe the relationship between
44     // two sets.
45 string GET_SET_CMD = "#get-set";
46     // retrieves the contents of the set named in first parameter.
47 string LIST_SET_NAMES_CMD = "#whichunz";
48     // retrieves the list of set names that exist.
49 string CLEAR_ALL_CMD = "#clearall";
50     // throws out all set definitions.
51 //////////////
52 // joins a list of parameters using the parameter sentinel for the library.
53 string wrap_parameters(list to_flatten)
54 { return llDumpList2String(to_flatten, HUFFWARE_PARM_SEPARATOR); }
55 //////////////
56
57 // these are the possible types of set relationships.
58 string POSSIBILITY_ONE_MEANING = "one meaning (don gcig)";  // same set.
59 string POSSIBILITY_THREE_POSSES = "proper subset (mu gsum)";  // set A contains set B or vice-versa.
60 string POSSIBILITY_FOUR_POSSES = "four zones (mu bzhi)";  // non-null intersection, mutual non-null differences.
61 string POSSIBILITY_MUTUAL_EXCLUDE = "mutually exclusive ('gal ba)";  // no members are shared between the sets.
62 string POSSIBILITY_ERRONEOUS = "erroneous";  // not a valid request.
63
64 // global variables...
65
66 list all_sets;  // a list of condensed lists.
67 list set_names;  // the names of each list in our list.
68
69 integer DEBUGGING = TRUE;  // produces noisy logging if true.
70
71 // finds index of named list.
72 integer find_set(string name_to_find)
73 {
74     return find_in_list(set_names, name_to_find);
75 }
76
77 // ensures that the list does not contain duplicate members.
78 list uniquify(list to_strain) {
79     integer i;
80     integer j;
81     for (i = 1; i < llGetListLength(to_strain); i++) {
82         string curr_i = llList2String(to_strain, i);
83         for (j = 0; j < i; j++) {
84             if (llList2String(to_strain, j) == curr_i) {
85                 // this one is a duplicate!  argh, remove it at index i.
86                 to_strain = chop_list(to_strain, 0, i - 1)
87                     + chop_list(to_strain, i + 1, llGetListLength(to_strain) - 1);
88                 i--;  // skip back since that element no longer exists.
89                 j = i*3;  // scoot j out to stop inner loop.
90             }
91         }
92     }
93     return to_strain;
94 }
95
96 // this version retrieves bare sets, not ones that are fluffed out from inclusions.
97 list simple_get_set_by_index(integer index)
98 {
99     if ( (index >= llGetListLength(set_names)) || (index < 0) ) return [];  // out of range.
100     return llParseString2List(llList2String(all_sets, index), [HUFFWARE_ITEM_SEPARATOR], []);
101 }
102
103
104 // adds or replaces the set that is called "name".
105 integer define_set(string name, list contents)
106 {
107     contents = uniquify(contents);
108     integer curr = find_set(name);
109     if (curr >= 0) {
110         // an existing entry should be updated.
111         remove_set_by_index(curr);
112     }
113     all_sets += [ wrap_item_list(contents) ];
114     set_names += [ name ];
115 //hmmm: may want to bomb out with false if too much space in use.
116     return TRUE;
117 }
118
119 // turns a combination of set linkages into the full set requested.
120 // assumes that the index is pre-validated!
121 list evaluate_set_components(integer index)
122 {
123     string wrap = llList2String(all_sets, index);
124     list to_return = llParseString2List(wrap, [HUFFWARE_ITEM_SEPARATOR], []);
125     // check for our "equal to set X" case.
126     if (llGetListLength(to_return) == 1) {
127         string memb = llList2String(to_return, 0);
128         if (llGetSubString(memb, 0, 0) == "=") {
129 //log_it("got a set assignment to other set called: " + llGetSubString(memb, 1, -1));
130             // we found a set that's equal to another.  look up its equivalent.
131             index = find_set(llGetSubString(memb, 1, -1));
132             if (index < 0) return [];  // unknown.
133             // got another set to check.
134             wrap = ""; to_return = [];
135             return evaluate_set_components(index);
136         }
137     }
138     if (llGetListLength(to_return) > 0) {
139         // scan for set includers.
140         integer i;
141         // must iterate forwards, since included sets can also include other sets.
142         for (i = 0; i < llGetListLength(to_return); i++) {
143             string curr = llList2String(to_return, i);
144             if (llGetSubString(curr, 0, 0) == "+") {
145                 // found that this set includes another one.  add in the second set,
146                 // but chop out the element we just looked at.
147                 to_return = chop_list(to_return, 0, i - 1)
148                     + chop_list(to_return, i + 1, llGetListLength(to_return) - 1)
149                     + simple_get_set_by_index(find_set(llGetSubString(curr, 1, -1)));
150                 i--;  // skip back so we don't omit the new element at i.
151             }
152         }
153     }
154     return to_return;
155 }
156
157 // retrieves the specified set from the list using its index.
158 list get_set_by_index(integer index)
159 {
160     if ( (index >= llGetListLength(set_names)) || (index < 0) ) return [];  // out of range.
161     return evaluate_set_components(index);
162 }
163
164 // retrieves the specified set from the list using its name.
165 list get_set_by_name(string set_name) { return get_set_by_index(find_set(set_name)); }
166
167 // drops the set listed at the "index".
168 integer remove_set_by_index(integer index)
169 {
170     if ( (index >= llGetListLength(set_names)) || (index < 0) ) return FALSE;  // out of range.
171     set_names = llDeleteSubList(set_names, index, index);
172     all_sets = llDeleteSubList(all_sets, index, index);
173     return TRUE;
174 }
175
176 // tosses the set called "set_name".
177 integer remove_set_by_name(string set_name) { return remove_set_by_index(find_set(set_name)); }
178
179 integer add_items(string name, list new_items)
180 {
181     new_items = uniquify(new_items);
182     integer curr = find_set(name);
183     if (curr < 0) return FALSE;  // unknown set.
184     list content = get_set_by_index(curr);
185     integer i;
186     for (i = 0; i < llGetListLength(new_items); i++) {
187         string new_item = llList2String(new_items, i);
188         integer found = find_in_list(content, new_item);
189         if (found < 0) {
190             // was not present yet so add it.
191             content += [ new_item ];
192         }
193     }
194     define_set(name, content);
195     return TRUE;
196 }
197
198 integer remove_items(string name, list dead_items)
199 {
200     integer curr = find_set(name);
201     if (curr < 0) return FALSE;  // unknown set.
202     list content = get_set_by_index(curr);    
203     integer i;
204     for (i = 0; i < llGetListLength(dead_items); i++) {
205         string dead_item = llList2String(dead_items, i);
206         integer found = find_in_list(content, dead_item);
207         if (found >= 0) {
208             // was not present yet so add it.
209             content = llDeleteSubList(content, found, found);
210         }
211     }
212     
213     define_set(name, content);
214     return TRUE;
215 }
216
217 // returns the union of two named sets.
218 list set_union(string set_a, string set_b)
219 {
220     integer where_a = find_set(set_a);
221     if (where_a < 0) return [];  // not known.
222     integer where_b = find_set(set_b);
223     if (where_b < 0) return [];
224     list to_return = get_set_by_index(where_a);
225     list adding = get_set_by_index(where_b);
226     integer i;
227     for (i = 0; i < llGetListLength(adding); i++) {
228         string curr = llList2String(adding, i);
229         integer where = find_in_list(to_return, curr);
230         if (where < 0) {
231             // wasn't found in to_return, so add it.
232             to_return += [ curr ];
233         }
234     }
235     return to_return;
236 }
237
238 // returns the difference of two named sets.
239 list set_difference(string set_a, string set_b)
240 {
241     integer where_a = find_set(set_a);
242     if (where_a < 0) return [];  // not known.
243     integer where_b = find_set(set_b);
244     if (where_b < 0) return [];
245     list to_return = get_set_by_index(where_a);
246     list adding = get_set_by_index(where_b);
247     integer i;
248     for (i = 0; i < llGetListLength(adding); i++) {
249         string curr = llList2String(adding, i);
250         integer where = find_in_list(to_return, curr);
251         if (where >= 0) {
252             // this item does exist in both, so remove it.
253             to_return = llDeleteSubList(to_return, where, where);
254         }
255     }
256     return to_return;
257 }
258
259 // returns the intersection of two named sets.
260 list set_intersection(string set_a, string set_b)
261 {
262     integer where_a = find_set(set_a);
263     if (where_a < 0) return [];  // not known.
264     integer where_b = find_set(set_b);
265     if (where_b < 0) return [];
266     list to_return = [];
267     list a_con = get_set_by_index(where_a);
268     list b_con = get_set_by_index(where_b);
269     integer i;
270     for (i = 0; i < llGetListLength(b_con); i++) {
271         string curr = llList2String(b_con, i);
272         integer where = find_in_list(a_con, curr);
273         if (where >= 0) {
274             // this item does exist in both, so include in the result.
275             to_return += [ curr ];
276         }
277     }
278     return to_return;
279 }
280
281 // returns TRUE if minor is a proper subset of major.
282 integer proper_subset(string minor_set, string major_set)
283 {
284     integer where_a = find_set(minor_set);
285     if (where_a < 0) return FALSE;  // not known.
286     integer where_b = find_set(major_set);
287     if (where_b < 0) return FALSE;
288     list min_con = get_set_by_index(where_a);
289     list maj_con = get_set_by_index(where_b);
290     // the list must be smaller to be a proper subset.
291     if (llGetListLength(min_con) >= llGetListLength(maj_con)) return FALSE;
292     // now make sure anything in the min is also in maj.  if not, it's not
293     // a subset at all.
294     integer i;
295     for (i = 0; i < llGetListLength(min_con); i++) {
296         string curr = llList2String(min_con, i);
297         integer where = find_in_list(maj_con, curr);
298         if (where < 0) return FALSE;
299     }
300     // every item was present.  yippee.
301     return TRUE;
302 }
303
304 // returns an assessment of the two sets in terms of the
305 // possible relationships between them.
306 string consider_mu(string set_a, string set_b)
307 {
308     // firstly, validate these set names.
309     integer where_a = find_set(set_a);
310     if (where_a < 0) return POSSIBILITY_ERRONEOUS;  // not known.
311     integer where_b = find_set(set_b);
312     if (where_b < 0) return POSSIBILITY_ERRONEOUS;
313     if (set_a == set_b) return POSSIBILITY_ONE_MEANING;  // easy check.
314     // check whether they have any common members at all.
315     list inter = set_intersection(set_a, set_b);
316     if (inter == []) return POSSIBILITY_MUTUAL_EXCLUDE;
317     // see if one set is contained in the other.
318     if (proper_subset(set_a, set_b)
319         || proper_subset(set_b, set_a) ) {
320         return POSSIBILITY_THREE_POSSES;
321     }
322     // resign ourselves to loading up the sets to check equality.
323     integer len_a = llGetListLength(get_set_by_index(where_a));
324     integer len_b = llGetListLength(get_set_by_index(where_b));
325     if (len_a == len_b) {
326         // this is the only way they can be equivalent.
327         // simple interpretation of this currently...
328         list diff = set_difference(set_a, set_b);
329         if (diff == []) return POSSIBILITY_ONE_MEANING;
330         // so now, although they are of equal length, we know they are not
331         // equal sets.
332     }
333     // if our logic is correct, there is only one option left.
334     return POSSIBILITY_FOUR_POSSES;
335 }
336
337 // this should be invoked from the link_message event handler to process the requests
338 // for whatever service this library script provides.
339 handle_link_message(integer sender, integer huff_id, string msg, key id)
340 {
341 //log_it("link msg: " + (string)sender + " " + (string)huff_id + " msg=" + msg + " id=" + (string)id);
342     // separate the list out
343     list parms = llParseString2List(id, [HUFFWARE_PARM_SEPARATOR], []);
344     
345     // we interpret the "msg" as a command.
346     if (msg == DEFINE_SET_CMD) {
347         string name = llList2String(parms, 0);
348         parms = llDeleteSubList(parms, 0, 0);
349         integer to_return = define_set(name, parms);
350         send_reply(sender, [ to_return ], msg);
351     } else if (msg == REMOVE_SET_CMD) {
352         string name = llList2String(parms, 0);
353         integer to_return = remove_set_by_name(name);
354         send_reply(sender, [ to_return ], msg);
355     } else if (msg == ADD_ELEMENTS_CMD) {
356         string name = llList2String(parms, 0);
357         parms = llDeleteSubList(parms, 0, 0);
358         integer to_return = add_items(name, parms);
359         send_reply(sender, [ to_return ], msg);
360     } else if (msg == CUT_ELEMENTS_CMD) {
361         string name = llList2String(parms, 0);
362         parms = llDeleteSubList(parms, 0, 0);
363         integer to_return = remove_items(name, parms);
364         send_reply(sender, [ to_return ], msg);
365     } else if (msg == INTERSECT_CMD) {
366         string name1 = llList2String(parms, 0);
367         string name2 = llList2String(parms, 1);
368         list to_return = set_intersection(name1, name2);
369         send_reply(sender, to_return, msg);
370     } else if (msg == UNION_CMD) {
371         string name1 = llList2String(parms, 0);
372         string name2 = llList2String(parms, 1);
373         list to_return = set_union(name1, name2);
374         send_reply(sender, to_return, msg);
375     } else if (msg == DIFFERENCE_CMD) {
376         string name1 = llList2String(parms, 0);
377         string name2 = llList2String(parms, 1);
378         list to_return = set_difference(name1, name2);
379         send_reply(sender, to_return, msg);
380     } else if (msg == WHAT_MU_CMD) {
381         string name1 = llList2String(parms, 0);
382         string name2 = llList2String(parms, 1);
383         string to_return = consider_mu(name1, name2);
384         send_reply(sender, [ to_return ], msg);
385     } else if (msg == GET_SET_CMD) {        
386         string name = llList2String(parms, 0);
387         send_reply(sender, get_set_by_name(name), msg);
388     } else if (msg == LIST_SET_NAMES_CMD) {
389         send_reply(sender, set_names, msg);
390     } else if (msg == CLEAR_ALL_CMD) {
391         set_names = [];
392         all_sets = [];
393         send_reply(sender, [ 1 ], msg);
394     } else {
395         log_it("unknown set command: msg=" + msg + " id=" + (string)id);
396     }
397 }
398
399
400 //////////////
401 //
402 // from hufflets...
403
404 // diagnostic hufflets...
405
406 integer debug_num = 0;
407
408 // a debugging output method.  can be disabled entirely in one place.
409 log_it(string to_say)
410 {
411     debug_num++;
412     // tell this to the owner.    
413     llOwnerSay(llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
414     // say this on an unusual channel for chat if it's not intended for general public.
415 //    llSay(108, llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
416     // say this on open chat that anyone can hear.  we take off the bling for this one.
417 //    llSay(0, to_say);
418 }
419
420 //////////////
421
422 // string processing hufflets...
423
424 // the string processing methods are not case sensitive.
425   
426 // returns the index of the first occurrence of "pattern" inside
427 // the "full_string".  if it is not found, then a negative number is returned.
428 integer find_substring(string full_string, string pattern)
429 { return llSubStringIndex(llToLower(full_string), llToLower(pattern)); }
430
431 // returns TRUE if the "prefix" string is the first part of "compare_with".
432 integer is_prefix(string compare_with, string prefix)
433 { return find_substring(compare_with, prefix) == 0; }
434
435 //////////////
436
437 // sim-related hufflets...
438
439 // returns TRUE if the value in "to_check" specifies a legal x or y value in a sim.
440 integer valid_sim_value(float to_check)
441 {
442     if (to_check < 0.0) return FALSE;
443     if (to_check >= 257.0) return FALSE;
444     return TRUE;
445 }
446
447 // returns TRUE if the "to_check" vector is a location outside of the current sim.
448 integer outside_of_sim(vector to_check)
449 {
450     return !valid_sim_value(to_check.x) || !valid_sim_value(to_check.y);
451 }
452
453 //////////////
454
455 // list processing hufflets...
456
457 // locates the string "text" in the list to "search_in".
458 integer find_in_list(list search_in, string text)
459
460     integer len = llGetListLength(search_in);
461     integer i; 
462     for (i = 0; i < len; i++) { 
463         if (llList2String(search_in, i) == text) 
464             return i; 
465     } 
466     return -1;
467 }
468
469 // removes the entry at "index" and instead inserts the list "to_insert"
470 // at that position.
471 list replace_entry(list to_modify, integer index, list to_insert)
472 {
473     if (llGetListLength(to_modify) == 0) return to_insert;  // special case for empty.
474     return llListReplaceList(to_modify, to_insert, index, index);
475 }
476
477 // returns the portion of the list between start and end, but only if they are
478 // valid compared with the list length.  an attempt to use negative start or
479 // end values also returns a blank list.
480 list chop_list(list to_chop, integer start, integer end)
481 {
482     integer last_len = llGetListLength(to_chop) - 1;
483     if ( (start < 0) || (end < 0) || (start > last_len) || (end > last_len) ) return [];
484     return llList2List(to_chop, start, end);
485 }
486
487 //////////////
488
489 // joins a list of sub-items using the item sentinel for the library.
490 string wrap_item_list(list to_wrap)
491 { return llDumpList2String(to_wrap, HUFFWARE_ITEM_SEPARATOR); }
492
493 // handles when blank strings need to come through the pipe.
494 string wrap_blank_string(string to_wrap)
495 {
496     if (llStringLength(to_wrap)) return to_wrap;  // that one is okay.
497     return "\"\"";  // return a quoted nothing as a signal for a blank.
498 }
499
500 // undoes a previously wrapped blank string.
501 string interpret_blank_string(string to_unwrap)
502 {
503     if (to_unwrap == "\"\"") return "";  // that was an encoded blank.
504     return to_unwrap;  // no encoding.
505 }
506
507 // a simple version of a reply for a command that has been executed.  the parameters
508 // might contain an outcome or result of the operation that was requested.
509 send_reply(integer destination, list parms, string command)
510 {
511 //log_it("reply set: " + dump_list(parms));
512     llMessageLinked(destination, SET_COMPARATOR_HUFFWARE_ID + REPLY_DISTANCE, command,
513         llDumpList2String(parms, HUFFWARE_PARM_SEPARATOR));
514 }
515
516 // returns a printable form of the list.
517 string dump_list(list to_show)
518 {
519     integer len = llGetListLength(to_show);
520     integer i;
521     string text;
522     for (i = 0; i < len; i++) {
523         string next_line = llList2String(to_show, i);
524         if (find_substring(next_line, " ") >= 0) {
525             // this guy has a space in it, so quote it.
526             next_line = "'" + next_line + "'";
527         }
528         text += next_line;
529         if (i < len - 1) text += " ";
530     }
531     return text;
532 }
533
534 //////////////
535
536 default {
537     state_entry() { if (llSubStringIndex(llGetObjectName(), "huffotronic") < 0) state real_default; }
538     on_rez(integer parm) { state rerun; }
539 }
540 state rerun { state_entry() { state default; } }
541
542 state real_default
543 {
544     state_entry()
545     {
546 //        log_it("memory left " + (string)llGetFreeMemory());
547 //hmmm: turn this into a report function.
548     }
549     
550     link_message(integer sender, integer num, string str, key id) {
551         if (num != SET_COMPARATOR_HUFFWARE_ID) return;
552         handle_link_message(sender, num, str, id);
553     }
554 }