Index: bindings/ruby/lib/mooix/thing.rb =================================================================== --- bindings/ruby/lib/mooix/thing.rb (revision 23) +++ bindings/ruby/lib/mooix/thing.rb (working copy) @@ -343,47 +343,47 @@ lock end -=begin ---- Thing.prettyname -Returns a name suitable for pretty-printing. Adds an article if one is defined, else adds nothing. - :Returns: - pretty-printable name for this. -=end - - def prettyname - if self.has?("article") and self.article != nil - return "#{article} #{name}" - else - return "#{name}" - end - end - -=begin ---- Thing.prettylist(objects) -Prepares ((|objects|)) for pretty-printing. - :Parameters: - : ((|objects|)) - list of objects or strings whose value is to be pretty-printed. - :Returns: - string listing all objects and strings contained within ((|objects|)) in a format suitable for printing. -=end - - def prettylist(objects) - return "nothing" if objects.length == 0 - result = "" - objects.each_index do |x| - if objects[x] == self - result += "you" - else - if objects[x].class != String - result += "#{objects[x].prettyname}" - else - result += "#{objects[x]}" - end - end - result += ", " if x <= (objects.length-3) - result += " and " if x == (objects.length-2) - end - return result - end +## =begin +## --- Thing.prettyname +## Returns a name suitable for pretty-printing. Adds an article if one is defined, else adds nothing. +## :Returns: +## pretty-printable name for this. +## =end +## +## def prettyname +## if self.has?("article") and self.article != nil +## return "#{article} #{name}" +## else +## return "#{name}" +## end +## end +## +## =begin +## --- Thing.prettylist(objects) +## Prepares ((|objects|)) for pretty-printing. +## :Parameters: +## : ((|objects|)) +## list of objects or strings whose value is to be pretty-printed. +## :Returns: +## string listing all objects and strings contained within ((|objects|)) in a format suitable for printing. +## =end +## +## def prettylist(objects) +## return "nothing" if objects.length == 0 +## result = "" +## objects.each_index do |x| +## if objects[x] == self +## result += "you" +## else +## if objects[x].class != String +## result += "#{objects[x].prettyname}" +## else +## result += "#{objects[x]}" +## end +## end +## result += ", " if x <= (objects.length-3) +## result += " and " if x == (objects.length-2) +## end +## return result +## end end Index: bindings/python/mooix/variables.py =================================================================== --- bindings/python/mooix/variables.py (revision 23) +++ bindings/python/mooix/variables.py (working copy) @@ -24,7 +24,7 @@ # this gets set at compile time try: - MOOROOT = MOOROOT_FROM_BUILD + MOOROOT = '/var/lib/mooix' except NameError: MOOROOT = None Index: bindings/python/mooix/Thing.py =================================================================== --- bindings/python/mooix/Thing.py (revision 23) +++ bindings/python/mooix/Thing.py (working copy) @@ -31,8 +31,8 @@ """A generic exception for something going wrong.""" pass -def _prettyname(thing): - return (thing.article and '%s ' % thing.article) + thing.name +## def _prettyname(thing): +## return (thing.article and '%s ' % thing.article) + thing.name class Thing(object): """This is the mooix object class for Python. It provides a standard class @@ -437,24 +437,24 @@ stacklevel = 2) return None - def prettyname(self): - """prettyname() -> the object's name with any article prepended""" - return _prettyname(self) +## def prettyname(self): +## """prettyname() -> the object's name with any article prepended""" +## return _prettyname(self) +## +## def prettylist(self, object_list): +## """prettylist(object_list) -> a pretty-printed list of objects +## +## Generates a very pretty-printed list of objects, and returns it. +## The object it's run on will appear in the list as "you".""" +## object_list = tolist(object_list) +## if object_list: +## return (len(object_list) > 2 +## and ', ' +## or ' and ').join([ _prettyname(o) +## for o in object_list +## if isinstance(o, Thing) ]) +## else: +## return 'nothing' - def prettylist(self, object_list): - """prettylist(object_list) -> a pretty-printed list of objects - Generates a very pretty-printed list of objects, and returns it. - The object it's run on will appear in the list as "you".""" - object_list = tolist(object_list) - if object_list: - return (len(object_list) > 2 - and ', ' - or ' and ').join([ _prettyname(o) - for o in object_list - if isinstance(o, Thing) ]) - else: - return 'nothing' - - __all__ = [ 'MooixError', 'Thing', 'LOCK_UN', 'LOCK_SH', 'LOCK_EX', 'LOCK_NB' ] Index: bindings/perl/lib/Mooix/Thing.pm =================================================================== --- bindings/perl/lib/Mooix/Thing.pm (revision 23) +++ bindings/perl/lib/Mooix/Thing.pm (working copy) @@ -90,8 +90,9 @@ Mooix::Thing also exports into your namespace a function called "fail". This function can be used by verbs (and occasional other methods) to exit with a -numeric exit code that indicates failure, and at the same time return a value -to the caller. +numeric exit code that indicates failure. It takes no arguments. +Before running it, you should use $this->msg() to let the user know +what happened. =cut @@ -118,7 +119,10 @@ # no strict 'refs'; *debuglog = *_debuglog_null; *{"${callpkg}::fail"} = sub { - print join("\n",map { "\"$_\"" } @_)."\n"; + if( @_ ) + { + print "UNTRANTSLATED FAIL MESSAGE!: ". join("\n",map { "\"$_\"" } @_)."\n"; + } exit 10; # FAIL }; *{"${callpkg}::run"} = sub { @@ -733,36 +737,45 @@ return 1; } -=item prettylist +## =item prettylist +## +## Generates a very pretty-printed list of objects, and returns it. The +## object it's run on will appear in the list as "you". +## +## =cut +## +## sub prettylist { +## my $this=shift; +## my $avatar=shift; +## my @objects=@_; +## +## # Get the list seperators for this language. +## my $list_sep = $this->language->list_seperator; +## my $list_sep_end = $this->language->list_seperator_last; +## +## return "nothing" if ! @objects; +## @objects = map { $_->prettyname( recipient => $this, avatar => $avatar ) } +## grep ref, @objects; +## $objects[$#objects] = $list_sep_end . $objects[$#objects] if @objects > 1; +## return join((@objects > 2) ? $list_sep : '', @objects); +## } -Generates a very pretty-printed list of objects, and returns it. The -object it's run on will appear in the list as "you". +## =item prettyname +## +## Returns the object's name with any article prepended. +## +## =cut +## +## sub prettyname { +## my $this=shift; +## my $avatar=shift; +## my $a = $this->article; +## $a.="" if length $a; +## my $text = $a.$this->name; +## $text = $this->dexml( avatar => $avatar, text => $text ); +## return $text; +## } -=cut - -sub prettylist { - my $this=shift; - my @objects=@_; - - return "nothing" if ! @objects; - @objects = map { $_ == $this ? 'you' : $_->prettyname } - grep ref, @objects; - $objects[$#objects] = 'and '.$objects[$#objects] if @objects > 1; - return join((@objects > 2) ? ', ' : ' ', @objects); -} - -=item prettyname - -Returns the object's name with any article prepended. - -=cut - -sub prettyname { - my $a = $_[0]->article; - $a.=" " if length $a; - return $a.$_[0]->name; -} - =item untaint If perl is run with taint checking enabled, and some method returns a mooix @@ -848,7 +861,11 @@ my $fh; if (! open ($fh, $file)) { - $this->croak($file); + # Can't use croak here! This is because croak + # *itself* does (many) field reads, so infinite + # loops can result! + ## $this->croak($file); + die "Failure in _readfield reading $file.\n"; } if (wantarray) { my $sticky=-k _; Index: bindings/c/moomethod.h =================================================================== --- bindings/c/moomethod.h (revision 23) +++ bindings/c/moomethod.h (working copy) @@ -3,6 +3,14 @@ * It's very incomplete so far. It doesn't try to be pseudo-OO (yet). */ +/* For information on how to pass values out, read "help + * methods-lowlevel". + */ + +/* To print debugging messages, send stuff to stderr. It's + * primitive, but it'll get sent to the calling session. + */ + #include #include #include @@ -65,7 +73,9 @@ * a true value (1) */ int truefield (object *obj, const char *field); /* Gets and returns a field of the current object. Does not do inheritence, - * and only returns the first line of the field. */ + * and only returns the first line of the field. Can be used on the + * return value of fieldfile, however, which makes it not entirely + * useless. */ char *getfield (const char *field); /* Sets a field of the current object to a value. Returns true if the set * succeeds. */ @@ -94,3 +104,27 @@ /* Read a line of any size and return a malloced string, or NULL on eof. */ char *mooix_getline (FILE *f, int killquotes); + +/* Generate a prettified name for an object, from the POV of the + * recipient. */ +char *prettyname( object *obj, object *recipient ); + +/* Read all parameters, return NULL terminated array. */ +param **getparams( void ); + +/* Look up a parameter from an array by name. */ +char *findparam (const char *key, param **params); + +/* Remove xml tags from the text. is supported; marked up + * in bold down the stream, all else are ignored. Also handle + * entities. In the vast majority of cases, uses dexml_recipient, + * which is an avatar, to determine the preferred language, but in + * some cases (read: parsers), uses the language argument instead. + */ +char *dexml( char *text, object *avatar_ptr, char *language ); + +/* Just like runmethod, but takes a param structure list instead of a bunch of strings */ +FILE *runmethod_param( object *obj, const char *method, param **params ); + +/* Runs prettyname as though a third-person perspective pertains */ +char *other_prettyname( object *obj, object *recipient ); Index: bindings/c/moomethod.c =================================================================== --- bindings/c/moomethod.c (revision 23) +++ bindings/c/moomethod.c (working copy) @@ -13,400 +13,1197 @@ #include #include #include +#include +#include +#include + +#include + #include "moomethod.h" int methinit (void) { - char *dir = getenv("THIS"); - if (dir == NULL) - return 0; - return chdir(getenv("THIS")); + char *dir = getenv("THIS"); + if (dir == NULL) + return 0; + return chdir(getenv("THIS")); } void freeparam (param *param) { - free(param->name); - free(param->value); - free(param); + free(param->name); + free(param->value); + free(param); } char *mooix_getline (FILE *f, int killquotes) { - int size = 0; - char *ret = NULL; + int size = 0; + char *ret = NULL; - if (feof(f)) - return NULL; - - do { - ret = realloc(ret, size + 128 + 1); - if (! fgets(ret + size, 128, f)) { - if (size == 0) { - free(ret); - return NULL; /* reached eof with empty string */ - } - else { - ret[size]='\0'; - break; - } - } - size = strlen(ret); - } while (size > 0 && ret[size - 1] != '\n'); + if (feof(f)) + return NULL; - /* remove trailing newline */ - if (ret[size - 1] == '\n') - ret[--size] = '\0'; - - /* Remove quotes? */ - if (killquotes) { - if (size > 1 && ret[0] == '"' && ret[size - 1] == '"') { - ret[--size] = '\0'; - memmove(ret, ret + 1, size); /* left shift by one char */ - } + do { + ret = realloc(ret, size + 128 + 1); + if (! fgets(ret + size, 128, f)) { + if (size == 0) { + free(ret); + return NULL; /* reached eof with empty string */ + } + else { + ret[size]='\0'; + break; + } } - return ret; + size = strlen(ret); + } while (size > 0 && ret[size - 1] != '\n'); + + /* remove trailing newline */ + if (ret[size - 1] == '\n') + ret[--size] = '\0'; + + /* Remove quotes? */ + if (killquotes) { + if (size > 1 && ret[0] == '"' && ret[size - 1] == '"') { + ret[--size] = '\0'; + memmove(ret, ret + 1, size); /* left shift by one char */ + } + } + return ret; } char *getkey () { - return mooix_getline(stdin, 1); + return mooix_getline(stdin, 1); } char *escape (const char *s) { - /* Change embedded newlines to \\n, and double slashes. Add quotes. */ - if (strchr(s, '\n') || strchr(s, '\\')) { - char *q, *t = malloc(strlen(s) * 2 + 3); - const char *p; - - for (p = s, q = t + 1; p[0] != '\0'; p++, q++) { - if (p[0] == '\n') { - q[0] = '\\'; - q[1] = 'n'; - q++; - } - else if (p[0] == '\\') { - q[0] = '\\'; - q[1] = '\\'; - q++; - } - else { - q[0] = p[0]; - } - } - t[0] = q[0] = '"'; - q[1] = '\0'; - return t; + /* Change embedded newlines to \\n, and double slashes. Add quotes. */ + if (strchr(s, '\n') || strchr(s, '\\')) { + char *q, *t = malloc(strlen(s) * 2 + 3); + const char *p; + + for (p = s, q = t + 1; p[0] != '\0'; p++, q++) { + if (p[0] == '\n') { + q[0] = '\\'; + q[1] = 'n'; + q++; + } + else if (p[0] == '\\') { + q[0] = '\\'; + q[1] = '\\'; + q++; + } + else { + q[0] = p[0]; + } } - else { - int len = strlen(s); - char *t = malloc(len + 3); - t[0] = '"'; - strcpy(t+1, s); - t[0] = t[len + 1] = '"'; - t[len + 2] = '\0'; - return t; - } + t[0] = q[0] = '"'; + q[1] = '\0'; + return t; + } + else { + int len = strlen(s); + char *t = malloc(len + 3); + t[0] = '"'; + strcpy(t+1, s); + t[0] = t[len + 1] = '"'; + t[len + 2] = '\0'; + return t; + } } char *unescape (char *s) { - char *p = s; - - while (p && (p = strstr(p, "\\"))) { - int len = strlen(s); + char *p = s; - if (p[1] == '\\') { - /* memmove below will remove first slash */ - } - else if (p[1] == 'n' && (p == s || p[-1] != '\\')) { - /* Turn "\n" into a literal newline. */ - p[1] = '\n'; - } - else { - p++; - continue; - } - - /* Copy remainder of line over slash. */ - memmove(p, p+1, len - (p - s) + 1); - p++; + while (p && (p = strstr(p, "\\"))) { + int len = strlen(s); + + if (p[1] == '\\') { + /* memmove below will remove first slash */ } - - return s; + else if (p[1] == 'n' && (p == s || p[-1] != '\\')) { + /* Turn "\n" into a literal newline. */ + p[1] = '\n'; + } + else { + p++; + continue; + } + + /* Copy remainder of line over slash. */ + memmove(p, p+1, len - (p - s) + 1); + p++; + } + + return s; } char *fgetvalue (FILE *f) { - return unescape(mooix_getline(f, 1)); + return unescape(mooix_getline(f, 1)); } char *getvalue () { - return fgetvalue(stdin); + return fgetvalue(stdin); } char **fgetallvals (FILE *f) { - int size=16, count=0; - char **ret=malloc(size * sizeof(char *)); - char *s; - - while ((s = fgetvalue(f))) { - ret[count] = s; - count++; - if (count >= size) { - size *= 2; - ret = realloc(ret, size * sizeof(char *)); - } + int size=16, count=0; + char **ret=malloc(size * sizeof(char *)); + char *s; + + while ((s = fgetvalue(f))) { + ret[count] = s; + count++; + if (count >= size) { + size *= 2; + ret = realloc(ret, size * sizeof(char *)); } - ret[count] = NULL; - return ret; + } + ret[count] = NULL; + return ret; } char **getallvals () { - return fgetallvals(stdin); + return fgetallvals(stdin); } param *getparam (void) { - param *ret = malloc(sizeof(param)); - - ret->name = NULL; - ret->value = NULL; - - if ((ret->name = getkey()) == NULL || + param *ret = malloc(sizeof(param)); + + ret->name = NULL; + ret->value = NULL; + + if ((ret->name = getkey()) == NULL || (ret->value = getvalue()) == NULL) { - freeparam(ret); - return NULL; - } + freeparam(ret); + return NULL; + } - return ret; + return ret; } +/* Read all parameters, return NULL terminated array */ +param **getparams( void ) { /* {{{ */ + /* Holds the parameters passed to this method. */ + param **params; + + param *p; + int numparams=4; + int curparam=0; + + params = malloc(sizeof(param *) * numparams + 1 ); + while ((p = getparam())) + { + //fprintf( stderr, "param: %s, %s.\n", p->name, p->value ); + params[curparam++]=p; + if (curparam >= numparams) + { + numparams = numparams * 2; + params=realloc(params, sizeof(param *) * (numparams + 1)); + } + } + params[curparam]=NULL; + + return params; +} /* }}} */ + +/* Look up a parameter from an array by name. */ +char *findparam (const char *key, param **params) { /* {{{ */ + /* TODO: optimize. hash? tsearch? */ + int i; + for (i=0; params[i] != NULL; i++) + { + if (strcmp(key, params[i]->name) == 0) + { + return params[i]->value; + } + } + return NULL; +} /* }}} */ + int truefield (object *obj, const char *field) { - char *file, *value; - - file = fieldfile(obj, field); - if (! file) - return 0; + char *file, *value; - value = getfield(file); - if (! value) - return 0; - if (! strlen(value)) - return 0; - if (strcmp(value, "0") == 0) - return 0; - else - return 1; + file = fieldfile(obj, field); + if (! file) + return 0; + + value = getfield(file); + if (! value) + return 0; + if (! strlen(value)) + return 0; + if (strcmp(value, "0") == 0) + return 0; + else + return 1; } char *getfield (const char *field) { - char *ret; - FILE *f = fopen(field, "r"); - if (f == NULL) - return NULL; - ret = mooix_getline(f, 0); - fclose(f); - return ret; + char *ret; + FILE *f = fopen(field, "r"); + if (f == NULL) + return NULL; + ret = mooix_getline(f, 0); + fclose(f); + return ret; } int setfield (const char *field, const char *value) { - FILE *f = fopen(field, "w"); - if (f == NULL) - return 0; - fprintf(f, "%s", value); - fclose(f); - return 1; + FILE *f = fopen(field, "w"); + if (f == NULL) + return 0; + fprintf(f, "%s", value); + fclose(f); + return 1; } char *fieldfile (object *obj, const char *field) { - int size, ods, fs, len; - char *ret; - char *p; - struct stat buf; - int depth = 0; + int size, ods, fs, len; + char *ret; + char *p; + struct stat buf; + int depth = 0; - /* set up ret to hold obj->dir/field */ - ods = strlen(obj->dir); - fs = strlen(field); - len = ods + 1; - size = len + fs + 128; - ret=malloc(size * sizeof(char)); - ret[0]='\0'; - strcat(ret, obj->dir); - strcat(ret, "/"); - p = ret + ods + 1; - - for (;;) { - /* Add field to end and see if anything turns up. */ - len += fs; - if (len >= size) { - size *= 2; - ret=realloc(ret, size * sizeof(char)); - /* ret might move, and thus so must p */ - p = ret + len - fs; - } - strcat(ret, field); - if (stat(ret, &buf) == 0) { - return ret; - } - - /* jump back to end of directory */ - len -= fs; - p[0]='\0'; + /* set up ret to hold obj->dir/field */ + ods = strlen(obj->dir); + fs = strlen(field); + len = ods + 1; + size = len + fs + 128; + ret=malloc(size * sizeof(char)); + ret[0]='\0'; + strcat(ret, obj->dir); + strcat(ret, "/"); + p = ret + ods + 1; - /* Add parent/ to end; make sure there is a parent */ - len += 7; - if (len >= size) { - size *= 2; - ret=realloc(ret, size * sizeof(char)); - p = ret + len - 7; - } - strcat(ret, "parent/"); - p += 7; /* points to end of parent/ */ - if (stat(ret, &buf) != 0) { - free(ret); - return NULL; /* no more parents */ - } + for (;;) { + /* Add field to end and see if anything turns up. */ + len += fs; + if (len >= size) { + size *= 2; + ret=realloc(ret, size * sizeof(char)); + /* ret might move, and thus so must p */ + p = ret + len - fs; + } + strcat(ret, field); + if (stat(ret, &buf) == 0) { + return ret; + } - /* Just in case.. */ - depth++; - if (depth > 200) { - fprintf(stderr, "possible recursive parent loop: %s\n", ret); - exit(1); - } + /* jump back to end of directory */ + len -= fs; + p[0]='\0'; + + /* Add parent/ to end; make sure there is a parent */ + len += 7; + if (len >= size) { + size *= 2; + ret=realloc(ret, size * sizeof(char)); + p = ret + len - 7; } + strcat(ret, "parent/"); + p += 7; /* points to end of parent/ */ + if (stat(ret, &buf) != 0) { + free(ret); + return NULL; /* no more parents */ + } + + /* Just in case.. */ + depth++; + if (depth > 200) { + fprintf(stderr, "possible recursive parent loop: %s\n", ret); + exit(1); + } + } } object *derefobj (const char *s) { - object *ret; + object *ret; - if (! s || strncmp(s, "mooix:", 6) != 0) - return NULL; - - ret = malloc(sizeof(object)); - ret->dev = 0; - ret->dir = strdup(s + 6); + if (! s || strncmp(s, "mooix:", 6) != 0) + return NULL; - return ret; + ret = malloc(sizeof(object)); + ret->dev = 0; + ret->dir = strdup(s + 6); + + /* Clean newline from the dir */ + ret->dir[ strcspn( ret->dir, "\n" ) ] = '\0'; + + return ret; } object *getobj (char *s) { - object *ret = malloc(sizeof(object)); - ret->dev = 0; - ret->dir = s; - return ret; + object *ret = malloc(sizeof(object)); + ret->dev = 0; + ret->dir = s; + return ret; } void freeobj (object *obj) { - free(obj->dir); - free(obj); + free(obj->dir); + free(obj); } /* This is very similar to _runmethod in the Mooix::Thing perl module.. */ FILE **runmethod_raw (object *obj, const char *method) { - static FILE *ret[2]; - int pipe1[2], pipe2[2]; - int parent_rdr, child_wtr; - int child_rdr, parent_wtr; - pid_t pid; + static FILE *ret[2]; + int pipe1[2], pipe2[2]; + int parent_rdr, child_wtr; + int child_rdr, parent_wtr; + pid_t pid; - /* Parent and child communication pipes. */ - pipe(pipe1); - parent_rdr=pipe1[0]; - child_wtr=pipe1[1]; - pipe(pipe2); - child_rdr=pipe2[0]; - parent_wtr=pipe2[1]; + /* Parent and child communication pipes. */ + pipe(pipe1); + parent_rdr=pipe1[0]; + child_wtr=pipe1[1]; + pipe(pipe2); + child_rdr=pipe2[0]; + parent_wtr=pipe2[1]; - pid = fork(); - if (pid == -1) { - close(parent_rdr); - close(parent_wtr); - close(child_rdr); - close(child_wtr); - return NULL; - } - else if (pid != 0) { - ret[0]=fdopen(child_wtr, "w"); - ret[1]=fdopen(child_rdr, "r"); - - /* Ignore sigpipes, which can easily occur if the child is - * very quick to run and does not read its input. */ - signal(SIGPIPE, SIG_IGN); + pid = fork(); + if (pid == -1) { + close(parent_rdr); + close(parent_wtr); + close(child_rdr); + close(child_wtr); + return NULL; + } + else if (pid != 0) { + ret[0]=fdopen(child_wtr, "w"); + ret[1]=fdopen(child_rdr, "r"); - close(parent_rdr); - close(parent_wtr); + /* Ignore sigpipes, which can easily occur if the child is + * very quick to run and does not read its input. */ + signal(SIGPIPE, SIG_IGN); - return ret; + close(parent_rdr); + close(parent_wtr); + + return ret; + } + else { + char *qualmethod; + + close(child_rdr); + close(child_wtr); + + close(0); + dup2(parent_rdr, 0); + close(parent_rdr); + close(1); + dup2(parent_wtr, 1); + close(parent_wtr); + + if (chdir(obj->dir) != 0) + exit(1); + + qualmethod=fieldfile(getobj("."), method); + if (! qualmethod) { + exit(1); } + + if (getenv("THIS")) { /* in the moo */ + execlp(qualmethod, qualmethod, NULL); + } else { - char *qualmethod; - - close(child_rdr); - close(child_wtr); + execlp("runmeth", "runmeth", qualmethod, NULL); + } + fprintf(stderr, "failed to exec %s %s\n", obj->dir, qualmethod); + exit(1); + } +} - close(0); - dup2(parent_rdr, 0); - close(parent_rdr); - close(1); - dup2(parent_wtr, 1); - close(parent_wtr); +FILE *runmethod_param( object *obj, const char *method, param **params ) { + FILE *wtr, *rdr, **fds; - if (chdir(obj->dir) != 0) - exit(1); + fds = runmethod_raw(obj, method); + if (fds == NULL) + return NULL; + wtr = fds[0]; + rdr = fds[1]; - qualmethod=fieldfile(getobj("."), method); - if (! qualmethod) { - exit(1); - } - - if (getenv("THIS")) { /* in the moo */ - execlp(qualmethod, qualmethod, NULL); - } - else { - execlp("runmeth", "runmeth", qualmethod, NULL); - } - fprintf(stderr, "failed to exec %s %s\n", obj->dir, qualmethod); - exit(1); + /* Pass params to child. */ + if (params) { + int i; + for (i = 0; params[i] != NULL; i++) + { + fprintf(wtr, "%s\n", params[i]->name); + //fprintf(stderr, "%s\n", params[i]->name); + fprintf(wtr, "%s\n", params[i]->value); + //fprintf(stderr, "%s\n", params[i]->value); } -} + } + fclose(wtr); /* let child know we're done so it can run */ + return rdr; +} FILE *runmethod (object *obj, const char *method, char **params) { - FILE *wtr, *rdr, **fds; - - fds = runmethod_raw(obj, method); - if (fds == NULL) - return NULL; - wtr = fds[0]; - rdr = fds[1]; - - /* Pass params to child. */ - if (params) { - int i; - for (i = 0; params[i] != NULL; i++) - fprintf(wtr, "%s\n", params[i]); + FILE *wtr, *rdr, **fds; + + fds = runmethod_raw(obj, method); + if (fds == NULL) + return NULL; + wtr = fds[0]; + rdr = fds[1]; + + /* Pass params to child. */ + if (params) { + int i; + for (i = 0; params[i] != NULL; i++) + { + fprintf(wtr, "%s\n", params[i]); + //fprintf(stderr, "%s\n", params[i]); } - fclose(wtr); /* let child know we're done so it can run */ - - return rdr; + } + fclose(wtr); /* let child know we're done so it can run */ + + return rdr; } int statobj (object *obj) { - struct stat buf; - if (stat(obj->dir, &buf) != 0) - return 0; - obj->dev = buf.st_dev; - obj->ino = buf.st_ino; - return 1; + struct stat buf; + if (stat(obj->dir, &buf) != 0) + return 0; + obj->dev = buf.st_dev; + obj->ino = buf.st_ino; + return 1; } int objcmp (object *a, object *b) { - /* The stat info is cached between calls. */ - if (! a->dev) { - if (! statobj(a)) - return -1; + /* The stat info is cached between calls. */ + if (! a->dev) { + if (! statobj(a)) + return -1; + } + if (! b->dev) { + if (! statobj(b)) + return -1; + } + + /* Return as does strcmp. */ + if (a->dev != b->dev) + return (a->dev > b->dev) - (a->dev < b->dev); + else + return (a->ino > b->ino) - (a->ino < b->ino); +} + +/* Generate a prettified name for an object, from the POV of the + * recipient. other_pov means that the name is to be treated as + * though a third-person perspective pertained. + */ + +char *internal_prettyname( object *obj, object *recipient, int other_pov ) { /* {{{ */ + char *name, *article, *file; + struct stat buf; + + if( objcmp(obj, recipient) == 0 && ! other_pov ) + { + char *lang_field_file; + object *lang_obj; + char *pronoun; + + /* At this point, we need at least the final seperator. */ + + lang_field_file = fieldfile( recipient, "language" ); + + if( lang_field_file == NULL ) { + /* No language; can't pick a seperator. Error out. + * */ + fprintf( stderr, "Recipient %s has no language in prettyname.\n", recipient->dir ); + pronoun = malloc( 1024 * sizeof( char ) ); + sprintf( pronoun, "ERROR: Recipient %s has no language in prettyname.\n", recipient->dir ); + return pronoun; } - if (! b->dev) { - if (! statobj(b)) - return -1; + + lang_obj = getobj( lang_field_file ); + + pronoun = getfield( fieldfile( lang_obj, "second_person_singular_pronoun" ) ); + + return pronoun; + } + + file = fieldfile(obj, "name"); + if (! file) + return ""; + /* The name might be a method to be called with no parameters. + * Rarely, but worth the stat for consistency. */ + if (stat(file, &buf) != 0) + return ""; + if (((buf.st_mode & S_IXUSR) == S_IXUSR) || + ((buf.st_mode & S_IXGRP) == S_IXGRP) || + ((buf.st_mode & S_IXOTH) == S_IXOTH)) { + /* Only allow running of methods that are marked as safe. */ + if (! truefield(obj, ".name-safe")) { + return ""; } + else { + FILE *f = runmethod(obj, "name", NULL); + if (! f) + return ""; + name = fgetvalue(f); + fclose(f); + } + } + else { + name = getfield(file); + } - /* Return as does strcmp. */ - if (a->dev != b->dev) - return (a->dev > b->dev) - (a->dev < b->dev); - else - return (a->ino > b->ino) - (a->ino < b->ino); + /* Hmm, article could be a method too, but it seems a little silly + * to support that. */ + file = fieldfile(obj, "article"); + + if (! file) + { + return ""; + } + + article = dexml( getfield(file), recipient, "" ); + + if (! article || ! strlen(article)) + { + char *ret = malloc(strlen(article) + 1 + strlen(name) + 1 + 13); + sprintf(ret, "%s", name); + return dexml( ret, recipient, "" ); + } else { + char *ret = malloc(strlen(article) + 1 + strlen(name) + 1 + 13); + sprintf(ret, "%s %s", article, name); + return dexml( ret, recipient, "" ); + } +} /* }}} */ + +/* Wrapper for internal_prettyname with other_pov off */ +char *prettyname( object *obj, object *recipient ) +{ + return internal_prettyname( obj, recipient, 0 ); } + +/* Wrapper for internal_prettyname with other_pov on */ +char *other_prettyname( object *obj, object *recipient ) +{ + return internal_prettyname( obj, recipient, 1 ); +} + + +/* + * Everything from here down + * + * is support functions for dexml, which is rather complicated. It + * strips XML tags out based on the avatar's language and other + * factors. + * + */ + +//FILE *log_file; +int skip=0; +int skip_depth=0; +int trailing_space=0; +char *parse_result; +char *parse_lang_code; + +/* Marks that the next initial alphabetic character should be made + * upper case + */ +int uc_next=0; +/* Marks that this is a language in which sentence-initial + * characters are made upper case. + */ +int uc_initial_lang=0; + +/* Pull a list of language codes used in the current text. */ +char **find_text_lang_codes( char *text, int *num_codes ) +{ + int status; + /* Only two possible matches: the main match and the one + * sub-expression. We only care about the latter. + * */ + regmatch_t lang_matches[2]; + regex_t lang_re; + int offset=0; + + /* Truly it would be a happy day when a moo has stuff translated + * into 129 languages... + */ + char **lang_codes; + + /* 128 32 byte lang codes */ + lang_codes = malloc( 128 * sizeof( char * ) ); + lang_codes[0] = malloc( 32 * sizeof( char ) ); + lang_codes[0][0] = '\0'; + + if( regcomp( &lang_re, "[<]lang code=['\"]([^'\"<>]*)['\"][>]", REG_EXTENDED ) != 0 ) + { + return( NULL ); /* report error */ + } + + status = regexec( &lang_re, text, (size_t) 2, lang_matches, 0); + + /* If no matches found */ + if (status != 0) { + return NULL; + } + + //printf( "Test: %d, %d.\n", lang_matches[0].rm_so, status ); + + while( regexec( &lang_re, text+offset, 2, lang_matches, 0) == 0 && lang_matches[1].rm_so >= 0 ) /* Found a match */ + { + /* See if this one is new */ + int j = 0; + int found = 0; + + //printf( "Test1.5: %d, %d, %d.\n", lang_matches[1].rm_so, j, *num_codes ); + + for( j = 0; j < *num_codes; j++ ) + { + //printf( "Test1.75: %s.\n", lang_codes[j] ); + /* If this one does not match an old one */ + if( strncmp( lang_codes[j], + text + offset + lang_matches[1].rm_so, + lang_matches[1].rm_eo - lang_matches[1].rm_so + ) == 0 ) + { + found = 1; + } + } + //printf( "Test2: %d, %d, %d, %s.\n", j, *num_codes, found, lang_codes[*num_codes] ); + + /* If we didn't find a match */ + if( found == 0 ) + { + strncpy( lang_codes[*num_codes], + text + offset + lang_matches[1].rm_so, + lang_matches[1].rm_eo - lang_matches[1].rm_so + ); + + lang_codes[*num_codes][lang_matches[1].rm_eo - lang_matches[1].rm_so] = '\0'; + + //printf( "Test2.1: %d, %d, %d, %s.\n", j, *num_codes, found, lang_codes[*num_codes] ); + *num_codes = *num_codes + 1; + + lang_codes[*num_codes] = malloc( 32 * sizeof( char ) ); + lang_codes[*num_codes][0] = '\0'; + //printf( "Test2.2: %d, %d, %d, %s.\n", j, *num_codes, found, lang_codes[*num_codes] ); + } + + offset += lang_matches[1].rm_eo; /* Update the offset */ + } + + regfree( &lang_re ); + + return lang_codes; +} + +/* Looks at the language codes present in the text, and compares + * them with the language codes the user prefers, in order, + * attempting to find the best match. + * + * If no language codes are present in the text, sets + * parse_lang_code to the user's first language preference. + * + * If the user has no language preferences, sets parse_lang_code to + * the first language code found in the text, if any. + * + * Returns 0 if no language codes were found in the text (so we can + * avoid further processing), 1 otherwise. + * */ +void find_best_lang_code( char *text, object *dexml_recipient ) +{ + char **lang_codes; + char *lang_field_file; + char *code_field_file; + FILE *code_field_fp; + char language[256]; + char best_language[256]; + int num_codes=0; + object *lang_obj; + + parse_lang_code = malloc( 256 * sizeof( char ) ); + parse_lang_code[0] = '\0'; + + lang_codes = find_text_lang_codes( text, &num_codes ); + + //printf( "Test3: %s.\n", getenv( "THIS" ) ); + + lang_field_file = fieldfile( dexml_recipient, "language" ); + + if( lang_field_file == NULL ) { + fprintf( stderr, "No language field in avatar %s in dexml.\n", dexml_recipient->dir ); + /* No language preferences */ + strcpy( parse_lang_code, lang_codes[0] ); + return; + } + + lang_obj = getobj( lang_field_file ); + + code_field_file = fieldfile( lang_obj, "code" ); + + if( code_field_file == NULL ) { + /* No language preferences */ + strcpy( parse_lang_code, lang_codes[0] ); + return; + } + + /* What we really want is the language's code file */ + //printf( "Test4: %s.\n",code_field_file ); + + code_field_fp = fopen(code_field_file, "r"); + + if( code_field_fp == NULL ) { + /* No language preferences */ + strcpy( parse_lang_code, lang_codes[0] ); + return; + } + + //printf( "Test5.\n" ); + + /* Check for a match between the language's code and the ones we + * pulled from the text in question. We allow it to be + * mult-line in case one wants aliases or to match extended + * languages like en-uk + */ + while( fgets(language, 256, code_field_fp) != NULL ) + { + + /* This will only run on the first run of the while, as we + * return, so it will only act on the first line of the + * languages field. */ + if( num_codes == 0 ) + { + /* Call it the user's most preferred language; this will be + * used for initial upper case decision making. + * */ + strcpy( parse_lang_code, language ); + //printf("no lang codes found; parse langcode: %s.\n", parse_lang_code ); + return; + } + + /* Kill the newline. */ + language[ strspn( language, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-" ) ] = '\0'; + //printf( "Test6: %s.\n", language ); + int j; + + for( j = 0; j < num_codes; j++ ) + { + /* Look for a match between the codes in the text and + * the languages the avatar will accept, in order of + * acceptance. + * */ + if( strcmp( language, lang_codes[j] ) == 0 ) + { + strcpy( best_language, language ); + /* sprintf( parse_lang_code, "joy: We found %d lang codes; %s, %s; %s.\n", num_codes, lang_codes[0], lang_codes[1], best_language );*/ + //printf( "joy: We found %d lang codes; %s, %s; %s.\n", num_codes, lang_codes[0], lang_codes[1], best_language ); + + strcpy( parse_lang_code, best_language ); + return; + } + } + + } + + /* Else, just return whatever language we saw first in the text. + sprintf( parse_lang_code, "no joy; We found %d lang codes; %s, %s; %s.\n", num_codes, lang_codes[0], lang_codes[1], lang_codes[0] ); + * */ + //printf( "no joy; We found %d lang codes; %s, %s; %s.\n", num_codes, lang_codes[0], lang_codes[1], lang_codes[0] ); + + strcpy( parse_lang_code, lang_codes[0] ); + + //printf("langcode: %s.\n", parse_lang_code ); + + return; +} + + +void replaceWordInText(const char* find, const char* replace, char* text) +{ + /* Cook until done */ + while( 1 ) + { + char *beginning=strstr(text, find); + + if( beginning ) + { + memmove(beginning+strlen(replace), beginning+strlen(find), 1+strlen(beginning+strlen(find))); + strncpy(beginning, replace, strlen(replace)); + } else { + break; + } + } +} + +static void XMLCALL xml_start(void *data, const char *el, const char **attr) +{ + int i; + + //printf("start tag: %s\n", el); + + /* If we found a lang tag, process it looking to see if the code + * matches */ + if( strcmp( el, "lang" ) == 0 ) + { + //printf( "lang start tag found.\n" ); + /* Increase our depth within the skip block, if this is a + * valid lang tag */ + if( skip == 1 ) + { + //printf( "lang start tag found in skip block.\n" ); + /* Look for a code attribute; otherwise this is an + * invalid lang tag, so we ignore it. + */ + for (i = 0; attr[i]; i += 2) + { + //printf("start tag attr: %s='%s', %s\n", attr[i], attr[i + 1], parse_lang_code); + if( strcmp( attr[i], "code" ) == 0 ) + { + skip_depth++; + //printf( "Increasing skip depth to %d.\n", skip_depth ); + } + } + //printf( "done with lang start tag found in skip block.\n" ); + } else { + /* Not skipping, find out if we should be. */ + for (i = 0; attr[i]; i += 2) + { + //printf("start tag attr: %s='%s', %s\n", attr[i], attr[i + 1], parse_lang_code); + if( strcmp( attr[i], "code" ) == 0 ) + { + if( strcmp( attr[i + 1], parse_lang_code ) == 0 ) + { + //printf( "best lang found.\n" ); + } else { + //printf( "non-best lang found.\n" ); + skip = 1; + skip_depth = 0; + } + } + } + } + } else if( strcmp( el, "document" ) == 0 ) { + /* If tag is the artificially added tag, drop it + */ + ; + } else if( skip == 1 ) { + /* Drop all skipped tags, unless otherwise handled above */ + ; + } else if( strcmp( el, "mspace" ) == 0 ) { + /* is used where we want a space, but only if + * there isn't already one just in front. This is for + * articles; the article in one language might be blank, so + * we can't just put in spaces every time. + */ + if( ! trailing_space ) + { + strcat( parse_result, " " ); + } + } else if( strcmp( el, "initial" ) == 0 ) { + /* The tag is added by msg.c to point out the + * place where the next non-tagged character should be + * counted as in initial character in a string in languages + * that have that sort of thing. + */ + //printf( "Initial tag found.\n" ); + if( uc_initial_lang ) + { + //printf( "UC next set.\n" ); + uc_next = 1; + } + } else { + /* If the tag is not a tag we want to process, preserve it */ + + strcat( parse_result, "<" ); + strcat( parse_result, el ); + + for (i = 0; attr[i]; i += 2) + { + strcat( parse_result, " " ); + strcat( parse_result, attr[i] ); + strcat( parse_result, "=" ); + strcat( parse_result, attr[i + 1] ); + } + + strcat( parse_result, ">" ); + //printf( "Updated p_r: %s.\n", parse_result ); + } + + //printf("\n"); +} + +static void XMLCALL xml_end(void *data, const char *el) +{ + //printf("end tag: %s, %d, %d\n", el, skip, skip_depth); + + if( strcmp( el, "lang" ) == 0 ) + { + /* Decrease our depth within the skip block */ + if( skip == 1 ) + { + skip_depth--; + //printf( "Decreasing skip depth to %d.\n", skip_depth ); + } + + /* If this is the end of the outermost skip block, stop + * skipping. */ + if( skip_depth < 0 ) + { + skip = 0; + skip_depth = 0; + //printf( "Turning skip off.\n"); + } + } else if( strcmp( el, "document" ) == 0 ) { + /* If tag is the artificially added tag, drop it + */ + ; + } else if( skip == 1 ) { + /* Drop all skipped tags, unless otherwise handled above */ + ; + } else if( strcmp( el, "mspace" ) == 0 ) { + /* We only use the end tag. Discard. */ + ; + } else if( strcmp( el, "initial" ) == 0 ) { + /* We only use the end tag. Discard. */ + ; + } else { + /* If the tag is not a tag we want to process, preserve it */ + strcat( parse_result, "" ); + } + +} + +/* Upper cases the first alphabetic character in the string. */ +void upper_case_next( char *scratch ) +{ + /* Walk the string for the first alphabetic + * character and uppercase it. + * */ + int i; + + for( i = 0; i < strlen( scratch ); i++ ) + { + if( isalpha( scratch[i] ) ) + { + scratch[i] = toupper( scratch[i] ); + /* Reset the flag to upper-case the next character */ + uc_next = 0; + break; + } + } +} + +static void XMLCALL char_data_handler(void *data, const char *el, int len) +{ + char *scratch; + + /* Give extra space because of the < to < expansion. */ + scratch = malloc( ( ( len * 2 ) + 1 ) * sizeof( char ) ); + scratch[0] = '\0'; + + if( skip == 0 ) + { + //printf("YES char data: %d, %.*s\n", len, len, el); + + strncat( scratch, el, len ); + + /* If we need to upper case the next letter, go and do it. + */ + if( uc_next ) + { + upper_case_next( scratch ); + } + + /* Don't want to break privledged tag protection. */ + replaceWordInText( "<", "<", scratch ); + replaceWordInText( ">", ">", scratch ); + + strcat( parse_result, scratch ); + + /* Look for a space at the end of the text */ + if( isblank( el[ len - 1] ) ) + { + //printf( "Setting trailing space based on: %c from %.*s.\n", el[ len - 1], len, el ); + trailing_space = 1; + } else { + //printf( "Unsetting trailing space based on: %c from %.*s.\n", el[ len - 1], len, el ); + trailing_space = 0; + } + + //printf( "Updated p_r: %s.\n", parse_result ); + } else { + //printf("NO char data: %d, %.*s\n", len, len, el); + } + + free( scratch ); +} + +/* Attempt to parse text, putting the raw text into parse_result if + * an error occurs. + */ +void my_parse( char *text, int length ) +{ + char *original_parse_result_end; + char *fixed_text; + + XML_Parser p = XML_ParserCreate(NULL); + + if (! p) { + //fprintf(stderr, "Couldn't allocate memory for parser\n"); + exit(-1); + } + + XML_SetElementHandler(p, xml_start, xml_end); + XML_SetCharacterDataHandler(p, char_data_handler); + + /* Mark the current end of parse_result in case we need + * to blow away what we've done because of an XML error. + */ + original_parse_result_end = &parse_result[strlen(parse_result)]; + + fixed_text = malloc( ( ( length * sizeof( char ) ) + 128 ) * 2 ); + fixed_text[0] = '\0'; + + sprintf( fixed_text, "%.*s", length, text ); + + //fprintf( stderr, "my_parse langcode: %s.\n", parse_lang_code ); + //fprintf( stderr, "my_parse fixed text: %s.\n", fixed_text ); + + if( XML_Parse(p, fixed_text, strlen( fixed_text ), 1) == XML_STATUS_ERROR ) + { + /* Copy the de-tagged text to parse_result */ + original_parse_result_end[0] = '\0'; + strncat( parse_result, text, length ); + + /* Put the terminator back in. */ + text[length] = '\0'; + //printf( "parse result after error: %s.\n", parse_result ); + } + + /* Reset all parsing flags. */ + skip=0; + skip_depth=0; +} + +/* Remove xml tags from the text. is supported; marked up + * in bold alter down the stream, all else are ignored. Also handle + * entities. In the vast majority of cases, uses dexml_recipient, + * which is an avatar, to determine the preferred language, but in + * some cases (read: parsers), uses the language argument instead. + */ +char *dexml( char *text, object *dexml_recipient, char *language ) +{ + int len; + + //fprintf( stderr, "In dexml.\n", text ); + + if( text ) + { + len = strlen(text); + } else { + return ""; + } + + if (len == 0) + { + return ""; + } + + //fprintf( stderr, "dexml pre: %s.\n", text ); + + /* Allocate some extra space (double, in fact, plus some extra + * for the xml header crap) for XML processing to increase the + * size of the string + */ + parse_result = malloc( ( ( len * sizeof( char ) ) + 128 ) * 2 ); + + parse_result[0] = '\0'; + + /* Only check the avatar's language if we weren't passed an + * explicit language code. + */ + if( dexml_recipient && ( ! language || strlen( language ) == 0 ) ) + { + //fprintf( stderr, "dexml recip: %s.\n", dexml_recipient->dir ); + find_best_lang_code( text, dexml_recipient ); + } else { + if( language ) + { + //fprintf( stderr, "dexml lang: %s.\n", language ); + parse_lang_code = language; + } else { + return text; + } + } + + //fprintf( stderr, "still in dexml.\n" ); + + /* Make sure we have a terminating null before any wierd + * characters, like newline. Even in C there has to be a + * better way to do this. */ + parse_lang_code[ strspn( parse_lang_code, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-" ) ] = '\0'; + + //fprintf( stderr, "dexml parse_lang_code: %s.\n", parse_lang_code ); + + /* Don't run XML processing if we've got no lang codes at all. + */ + if( parse_lang_code != NULL && parse_lang_code[0] != '\0' ) + { + char *lang_field_file; + char *uc_initial_lang_file; + FILE *uc_initial_lang_fp; + + /* Set uc_initial to zero if we were passed a lang code; + * should only be used by short text blocks passed by + * parsers. + */ + if( language && strlen( language ) > 0 ) + { + uc_initial_lang = 0; + } else { + /* Only find out about upper case initial status if we + * weren't passed a language code. + */ + + /* Find the list of languages that have their initial + * letter upper-cased (i.e., most romance languages) */ + lang_field_file = fieldfile( dexml_recipient, "language" ); + + if( lang_field_file != NULL ) + { + object *lang_obj; + lang_obj = getobj( lang_field_file ); + + uc_initial_lang_file = fieldfile( lang_obj, "upper_case_initial" ); + + if( uc_initial_lang_file != NULL ) + { + //printf( "uc_initial_lang_file: %s.\n", uc_initial_lang_file ); + uc_initial_lang_fp = fopen(uc_initial_lang_file, "r"); + + if( uc_initial_lang_fp != NULL ) + { + char flag_string[8]; + + if( fgets(flag_string, 8, uc_initial_lang_fp) != NULL ) + { + int flag; + + /* Grab the field as an integer; it's either 0 or 1 */ + flag = strtol(flag_string, (char **)NULL, 10); + if( flag ) + { + uc_initial_lang = 1; + } + } + } + } + } + } + + //fprintf( stderr, "Language UC initial is %d.\n", uc_initial_lang ); + + my_parse( text, strlen(text) ); + + //fprintf( stderr, "dexml post: %s.\n", parse_result ); + return parse_result; + } else { + return text; + } +} Index: bindings/c/Makefile =================================================================== --- bindings/c/Makefile (revision 23) +++ bindings/c/Makefile (working copy) @@ -10,7 +10,7 @@ CFLAGS += -g -fPIC -DPIC -Wall $(LIB): $(objs) - $(CC) $(CFLAGS) -lc -shared -Wl,-soname -Wl,$(LIB).$(SONAME) $(objs) -o $(LIB).$(SONAME) + $(CC) $(CFLAGS) -lexpat -lc -shared -Wl,-soname -Wl,$(LIB).$(SONAME) $(objs) -o $(LIB).$(SONAME) rm -f $(LIB) $(LN_S) $(LIB).$(SONAME) $(LIB) Index: INSTALL =================================================================== --- INSTALL (revision 23) +++ INSTALL (working copy) @@ -58,6 +58,14 @@ /etc/ld.so.conf to list the directory the mooix libraries were installed into, and run ldconfig. + To install to somewhere other than the location the code + will be running at (something like a chroot or VServer, + where mood might be in /usr/sbin/mood but you want to + install to /chroot/path/usr/sbin/moot) run make install as + follows: + + make PREFIX=[wierd installation path] install + Initialization ============== Index: mooix.conf =================================================================== --- mooix.conf (revision 23) +++ mooix.conf (working copy) @@ -16,33 +16,33 @@ # running at a time in the moo, and more is better. Mood will only use # users in this space as scratch users if they have no entries in the # password file at the time it is started up. -LOWUID=31000 -HIGHUID=32000 +LOWUID=3000 +HIGHUID=4000 # Base of the moo object tree. Please note that you can change this at # build time, but it's fairly rooted to this location once installed. -MOOROOT=$(localstatedir)/lib/mooix +MOOROOT=/var/lib/mooix # Distributed objects. This directory holds all the static mooix # objects that are distributed with mooix. -DISTOBJ=$(libdir)/mooix +DISTOBJ=/usr/lib/mooix # These are objects that have the same powers as the moo admin, but more # limited purposes. Note that the order is significant; the first object # listed should be the one most often used. -MOOADMINOBJ=$(localstatedir)/lib/mooix/abstract/physics -MOOADMINOBJ=$(localstatedir)/lib/mooix/system/heartbeat +MOOADMINOBJ=/var/lib/mooix/abstract/physics +MOOADMINOBJ=/var/lib/mooix/system/heartbeat # This is the object that is the moo admin. It can modify any field of any # other object. -MOOADMINOBJ=$(localstatedir)/lib/mooix/system/admin +MOOADMINOBJ=/var/lib/mooix/system/admin # This string is prefixed to the user names of new users who register for # moo accounts. It should be short, since there are only 8 characters # available for the whole name on most systems. -MOOUSERPREFIX=m- +MOOUSERPREFIX= # Newly registered users will get this type of avatar. -PARENTAVATAR="$(localstatedir)/lib/mooix/abstract/avatar" +PARENTAVATAR="/var/lib/mooix/abstract/avatar" # This command is run at moo startup time. STARTHOOK="cd $MOOROOT/system/init && runmeth startup" @@ -55,7 +55,7 @@ # Some commands, like moologin, need a sanitized PATH, not the one from the # environment. This sets that path. -SAFEPATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:$(sbindir) +SAFEPATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/sbin # The following variables set hard resource limits for the mood daemon, and # the mooix methods it executes. @@ -89,10 +89,10 @@ # part of a single method. It is useful to prevent methods from fork bombing # the moo. This does not influence the number of methods that may run at # once. For that you can make the gap between HIGHUID and LOWUID be small. -RLIMIT_NPROC=10 +RLIMIT_NPROC=128 # # The maximum number of files that a process can open at a time. -RLIMIT_NOFILE=100 +RLIMIT_NOFILE=1024 # # The total amount of memory a process can get. #RLIMIT_AS= Index: MULTILINGUAL =================================================================== --- MULTILINGUAL (revision 0) +++ MULTILINGUAL (revision 0) @@ -0,0 +1,21 @@ +mooix now has full support for multilingual MOOs, that is, MOOs in +which different users are interacting with the MOO, and seeing the +MOO presented in, different languages. + +For details of how to set this up for new languages can be found by +doing "help multilingual" in the MOO itself. + +All of this work was done by Robin Powell, aka +rlpowell@digitalkingdom.org; all comments, questions, and bug +reports on the multilingual code should be directed to him. + +PLEASE NOTE: No work was done on UTF-8 or other high byte handling! + +The language I was using (Lojban, see http://www.lojban.org/) is +expressible in ASCII, and I don't know enough C or enough about +UTF-* to know how to make things work properly at the C level. + +I would be VERY HAPPY if someone else made mooix UTF-8 safe! + +-Robin Lee Powell, 2 Jan 2005 + Index: obj/mixin/mcp/simpleedit/receive_set =================================================================== --- obj/mixin/mcp/simpleedit/receive_set (revision 23) +++ obj/mixin/mcp/simpleedit/receive_set (working copy) @@ -15,5 +15,5 @@ } return $this->avatar->edit_finish(session => $this, value => $content, - id => $_{reference}); + id => $_{reference}, avatar => $this->avatar ); } Index: obj/mixin/parser/supply.msg =================================================================== --- obj/mixin/parser/supply.msg (revision 0) +++ obj/mixin/parser/supply.msg (revision 0) @@ -0,0 +1 @@ +The parser believes that you need to supply the following parts of speech: $incompletes. Index: obj/mixin/parser/pronouns =================================================================== --- obj/mixin/parser/pronouns (revision 23) +++ obj/mixin/parser/pronouns (working copy) @@ -1,31 +0,0 @@ -it -its -me -myself -I -my -here -you -your -her -she -he -him -his -us -our -them -their -this -these -that -those -everything -everythings -everyone -everyones -anything -anythings -all -any -each Index: obj/mixin/parser/wtf.msg =================================================================== --- obj/mixin/parser/wtf.msg (revision 0) +++ obj/mixin/parser/wtf.msg (revision 0) @@ -0,0 +1 @@ +I have no idea what you said. I see no verb. Index: obj/mixin/parser/grammar =================================================================== --- obj/mixin/parser/grammar (revision 23) +++ obj/mixin/parser/grammar (working copy) @@ -1,302 +0,0 @@ -#!/usr/bin/perl (more or less) - -# This file contains the Parse::RecDescent grammar used by the parser to -# deconstruct imperative sentences. -# -# The resulting parser builds and returns a parse tree. -# The form of the tree is a list of hashes (sentences). -# The sentance hashes can have keys named verb, direct_object, -# indirect_object, do_preposition, io_preposition, and quote -# (and a couple more weird ones). -# -# Thank god for HyperGrammar! -# - -# Handle compound sentences, and multiple sentences too. -input: sentence (sentence_separator sentence)(s?) sentence_punct(?) - { $item[2] ? [ $item[1], @{$item[2]} ] : [ $item[1] ] } -sentence_separator: /$/ | sentence_punct(?) coordinating_conjunction(s) | sentence_punct - -# All the sentence forms. The ordering is quite important. I've tried to -# put the most commonly used forms first, so they'll be faster. Note that -# the use of lookahead is important in getting those fast, commonly-used -# forms to not match on subsets of longer sentences. -# -# Once each sentence is parsed, a call to main::recent_obj() is made, -# passing in any recently referred to objects. This is generally used to -# set up the 'it' and 'them' prepositions, or similar. - -# Talking is quick to match. -sentence: verb quote ...sentence_separator - { { verb => $item[1], quote => $item[2] } } -# This form is used to invoke the name of an exit to use it. -# (It can also be used to answer some questions.) It needs to come before -# the verb direct_object form. Probably calling recent_obj here would just -# be confusing. -sentence: object ...sentence_separator - { { direct_object => $item[1] } } -# "sit down", "get up", etc. Has to come before the verb direct_object form. -sentence: verb preposition ...sentence_separator - { { verb => $item[1], preposition => $item[2] } } -# Probably the most common sentence form. -sentence: verb direct_object ...sentence_separator - { &::recent_obj(@{$item[2]}); - { verb => $item[1], direct_object => $item[2] } } -# This form is used to "pick up foo", etc. -sentence: verb do_preposition direct_object ...sentence_separator - { &::recent_obj(@{$item[3]}); - { verb => $item[1], do_preposition => $item[2], - direct_object => $item[3] } } -# This form is used in eg, "put it down" or "wind it up". -sentence: verb direct_object do_preposition ...sentence_separator - { &::recent_obj(@{$item[2]}); - { verb => $item[1], do_preposition => $item[3], - direct_object => $item[2] } } -# "put blah in foo", etc is quite common. -sentence: verb do_preposition(?) direct_object io_preposition(?) indirect_object - { &::recent_obj(@{$item[3]}); # which object? Dunno. :-/ - { verb => $item[1], do_preposition => $item[2][0], - direct_object => $item[3], io_preposition => $item[4][0], - indirect_object => $item[5] } } -# Not exactly sentences per se, but support answers to recently asked -# questions. That generally involves picking a choice from a list or -# answers, either by name or number. Or it might involve referring to a -# particular object, or be a prepositional phrase. -sentence: article(?) answer(s) ...sentence_separator - { { answer => $item{'answer(s)'} } } -sentence: article(?) number ...sentence_separator - { { number => $item{number} } } -sentence: do_preposition object ...sentence_separator - { { direct_object => $item{object}, - do_preposition => $item{do_preposition} } } -# Simple commands are way up there too (but must come after the simple -# question answer forms). -sentence: verb ...sentence_separator - { { verb => $item[1] } } - -# This is a gross special case for a few commands that take a field as -# their last argument. -fieldverb: /(show|showall|set|unset|edit|delete|usage|help|go|list)\b/i -# A special terminator is needed to disambiguate from things like -# "show ball then drop it", where "then" could be misinterpreted as a -# field. -# Must come before the verb quote direct_object form. -sentence: fieldverb do_preposition(?) possessive_object field ...sentence_separator - { &::recent_obj(@{$item[3]}); - { verb => $item[1], do_preposition => $item[2][0], - direct_object => $item[3], field => $item[4] } } -sentence: fieldverb do_preposition(?) possessive_object number field ...sentence_separator - { &::recent_obj(@{$item[3]}); - { verb => $item[1], do_preposition => $item[2][0], - direct_object => $item[3], number => $item[4], field => $item[5] } } -# Used for the help command. -sentence: fieldverb do_preposition(?) field ...sentence_separator - { { verb => $item[1], do_preposition => $item[2][0], field => $item[3] } } - -# "say "blah" to him", "derive a "ball" from foo", etc. -# This is strictly speaking, an indirect object, not a direct object. -# However, it simplfies processing to treat it like a direct object. -sentence: verb do_preposition(?) article(?) quote io_preposition direct_object - { &::recent_obj(@{$item[6]}); - { verb => $item[1], quote => $item[4], - do_preposition => $item[5], direct_object => $item[6] } } - -# Now some declarative sentence forms. Matching a possessive object is -# expensive, so do it only once. -sentence: possessive_object declaration - { &::recent_obj(@{$item[1]}); - { direct_object => $item[1], %{$item[2]} } } - -# Stuff like "it's not hidden". -declaration: ess /\bnot\b/i field ...sentence_separator - { { verb => "is", field => $item[3], negated_verb => 1 } } -# "it's hidden", etc -declaration: ess field ...sentence_separator - { { verb => "is", field => $item[2] } } -# "I'm not benchmarked" -declaration: /'?m?\b/i /\bnot\b/i field ...sentence_separator - { { verb => "am", field => $item[3], negated_verb => 1 } } -# "I'm benchmarked" -declaration: /'?m?\b/i field ...sentence_separator - { { verb => "am", field => $item[2] } } -# Used, for example, to just say what a field's value is, to set it. -declaration: field verb quote - { { field => $item[1], verb => $item[2], quote => $item[3] } } -# Similar form can be used (by builders) to say that an object's field is a -# reference to another object. -declaration: field verb indirect_object - { { field => $item[1], verb => $item[2], indirect_object => $item[3] } } -# This is used to set metadata about fields. -declaration: field verb field number - { { field => $item[1], verb => $item[2], - metadata => $item[3], number => $item[4] } } -# Even a list of references could be set. -declaration: number field verb indirect_object - { { number => $item[1], field => $item[2], - verb => $item[3], indirect_object => $item[4] } } -# A number can also be given, if there are multiple values of a field. -declaration: number field verb quote - { { number => $item[1], field => $item[2], - verb => $item[3], quote => $item[4] } } -# This is used to set and unset boolean fields. -declaration: negated_verb field - { { verb => $item[1], field => $item[2], negated_verb => 1 } } -declaration: verb field - { { verb => $item[1], field => $item[2] } } - -# These forms are used by the signal command. -sentence: verb direct_object preposition(?) number - { &::recent_obj(@{$item[2]}); - { verb => $item[1], direct_object => $item[2], number => $item[4] } } -sentence: verb direct_object quote preposition(?) number - { &::recent_obj(@{$item[2]}); - { verb => $item[1], direct_object => $item[2], quote => $item[3], - number => $item[5] } } - -# And this is is used for dialing telephones. I suppose it could be used -# for signals too.. Like the verb quote direct_object form, the object is -# really indirect, but we'll call it the direct object for simplicity. -sentence: verb do_preposition(?) number io_preposition direct_object - { &::recent_obj(@{$item[5]}); - { verb => $item[1], number => $item[3], - do_preposition => $item[4], direct_object => $item[5] } } - -# "call me "Fred"", "rename me to "Fred"", etc. Must come after the -# declarative forms, otherwise the quote matches too freely. -sentence: verb do_preposition(?) direct_object io_preposition(?) quote - { &::recent_obj(@{$item[3]}); - { verb => $item[1], do_preposition => $item[2][0], - direct_object => $item[3], io_preposition => $item[4][0], - quote => $item[5] } } - -# This wacky form is used for digging. -sentence: verb quote io_preposition quote - { { verb => $item[1], quote => $item[2], io_preposition => $item[3], - quote2 => $item[4] } } # XXX there must be a better name than "quote2"? - -# These forms are used to do stuff with fields. -sentence: verb possessive_object field io_preposition quote - { &::recent_obj(@{$item[2]}); - { verb => $item[1], direct_object => $item[2], - field => $item[3], quote => $item[5] } } -sentence: verb possessive_object field io_preposition indirect_object - { &::recent_obj(@{$item[2]}); - { verb => $item[1], direct_object => $item[2], - field => $item[3], indirect_object => $item[5] } } -sentence: verb possessive_object number field io_preposition quote - { &::recent_obj(@{$item[2]}); - { verb => $item[1], direct_object => $item[2], - number => $item[3], field => $item[4], quote => $item[6] } } - -# For the eval command. -sentence: verb quote io_preposition field ...sentence_separator - { { verb => $item[1], quote => $item[2], io_preposition => $item[3], - field => $item[4] } } - -# This is a repeat of the simple sentence form, but it does not require an -# obvious separator. The only reason for this is to make reinjection work -# for stuff like "say hi" -- this parses the verb, then the "hi" is quoted -# and the lot is re-injected. -# This should be the last sentence type listed. -sentence: verb - { { verb => $item[1] } } - -# End of the entences, now on to the parts of speech.. - -direct_object: objectlist -indirect_object: object -# Allows for multiple prepositions to be used before a direct object. They -# are joined together into one. -do_preposition: preposition(s) - { join(" ", @{$item[1]}) } -io_preposition: preposition -objectlist: object (/(?:(?:,\s*)?and|,)/ object)(s?) - # Flatten the nested lists into one list ref. - { [ $item[2] ? ( @{$item[1]}, map { @{$_} } @{$item[2]} ) : @{$item[1]} ] } - -# "foo's bar" -object: basic_object ess object - { &::is_obj_in_obj($item[3], "", $item[1]) } -# "my bar" -object: basic_object object - { &::is_obj_in_obj($item[2], "", $item[1]) } -# "bar in foo". Note that multiple prepositions might be used; all must -# match. -object: basic_object preposition(s) object - { &::is_obj_in_obj($item[1], $item[2], $item[3]) } -# Quantifying the number of objects expected can resolve possible -# ambiguities. -object: /(a\b)?/ quantifier /(of\b)?/ object - { &::check_quantification($item{quantifier}, $item{object}) } -# Must some after the quantified object test, because "all" could be part -# of a quantification, or a preposition. -object: basic_object -# Another form of quantification, a trifle expensive. -object: number /(of)?/ basic_object - { &::check_quantification($item{number}, $item{basic_object}) } - -possessive_object: object ess - { $item[1] } - -# This is the set of simple ways to refer to an object, and is used as the -# base for both regular and possessive forms of objects. -basic_object: pronoun - { &::lookup_pronoun($item{pronoun}) } -basic_object: article(?) /mooix:([^ ]+)/ - { &::lookup_reference($1) } -basic_object: article(?) adjectivelist noun - { &::lookup_noun($item{noun}, $item{adjectivelist}) } -# This version is needed for cases like 'red guest', where red is a known -# adjective, but it's actually being used as part of the noun instead. -basic_object: article(?) noun - { &::lookup_noun($item{noun}) } -# A production without the article in front, in case the noun seems to strt -# with an article (probably due to user confusion). -basic_object: noun - { &::lookup_noun($item{noun}) } - -adjectivelist: - -number: /[-+.\w]+\b/ - # lookup_number is passed a textual representation of a number, and - # should return the number so represented, or undef on error - { { &::lookup_number($item[1]) } } - -# Single or double quoted text. Allow the closing quote to be left off, if -# the text extends to end of string without one. This also recognizes stuff -# bracketed by {..} as a quote. This special style is used by the shortcuts -# substitutions, to unambiguously quote text that may contain other quote -# characters. Quotes can have a comma before them. -quote: /,?\s*(?:"([^"]*)(?:"|$)|{(.*)})/ { $1.$2 } -# Things like object field names. Note that they cannot end in a period; -# that would be ambiguous with a period at the end of a sentence. -field: /[-_.+A-Za-z0-9]*[-_+A-Za-z0-9]/ - -# This only works for verbs like 'is' in declarative sentence forms. -negated_verb: verb /not\b/i - { $item[1] } -negated_verb: /($::verbs)n't\b/i - { $1 } - -# Some of the parts of speech are broken out into variables in main; -# these variables must be defined before asking the parser to parse -# something, and can be changed as needed between parsings w/o rebuilding -# the whole parser. This makes it easy to eg, populate $::nouns with all -# the names of all the objects the user could refer to. Set the variables -# to compiled regexp's, that | together the possibilities. Like: -# $::nouns=qr/cat|dog/; -preposition: /($::prepositions)\b/i -adjective: /($::adjectives)\b/i -noun: /($::nouns)\b/i -verb: /($::verbs)\b/i -# The \b is necessary, since "i" is a pronoun, and that could match at the -# start of other words. -pronoun: /($::pronouns)\b/i -# Matches answers to a recent question. -answer: /($::answers)/i -quantifier: /($::quantifiers)/i - -article: /(an|a|the)\b/i -coordinating_conjunction: /(and|then|next)\b/i -ess: /'?s?\b/i -sentence_punct: /[,;.!]+/ Index: obj/mixin/parser/confused.msg =================================================================== --- obj/mixin/parser/confused.msg (revision 0) +++ obj/mixin/parser/confused.msg (revision 0) @@ -0,0 +1 @@ +I'm sorry, either what you're asking to do isn't possible, or you've managed to confuse me. Index: obj/mixin/parser/pronouns.inf =================================================================== --- obj/mixin/parser/pronouns.inf (revision 23) +++ obj/mixin/parser/pronouns.inf (working copy) @@ -1,2 +0,0 @@ -This field needs to have a list of every pronoun the parser will -recognize. Index: obj/mixin/parser/prepositions =================================================================== --- obj/mixin/parser/prepositions (revision 23) +++ obj/mixin/parser/prepositions (working copy) @@ -1,60 +0,0 @@ -as -about -above -across -after -against -along -among -around -at -before -behind -below -beneath -beside -between -beyond -but -by -despite -down -during -except -for -from -inside -into -in -like -near -off -of -onto -on -outside -out -over -past -since -throughout -through -till -toward -to -underneath -under -until -upon -up -within -without -with -called -named -held -carried -away -using -front -top Index: obj/mixin/parser/compilegrammar =================================================================== --- obj/mixin/parser/compilegrammar (revision 23) +++ obj/mixin/parser/compilegrammar (working copy) @@ -1,20 +1,29 @@ #!/usr/bin/perl #use Mooix::Thing; use Parse::RecDescent; -run sub ($) { - my $this=shift; +run sub { + my $this=shift; + %_ = @_; + my $avatar=$_{avatar}; - # Only compile the grammar if it is newer than the compiled form. - my $gpm = $this->fieldfile("Grammar.pm"); - my $gra = $this->fieldfile("grammar"); - if (! $gpm || (stat($gpm))[9] < (stat($gra))[9]) { - # Output to "myGrammar", then rename, so the update is atomic. - Parse::RecDescent->Precompile(scalar $this->grammar, "myGrammar"); - rename("myGrammar.pm", "Grammar.pm") || die "rename: $!"; - # Mark it as a method. This prevents show from trying to display - # the whole thing.. - chmod(0755, "Grammar.pm"); - } + # Only compile the grammar if it is newer than the compiled form. + my $gpm = $this->fieldfile("Grammar.pm"); + my $gra = $avatar->language->fieldfile("grammar"); - return 1; + if (! $gpm || (stat($gpm))[9] < (stat($gra))[9]) { + + # Output to "my[code]Grammar", then rename, so the update is atomic. + my $gram = "my" . $avatar->language->code . "Grammar"; + my $pm = $avatar->language->code . "Grammar.pm"; + + Parse::RecDescent->Precompile( scalar $avatar->language->grammar, $gram ); + + rename( $gram . ".pm", $pm ) || die "rename: $!"; + + # Mark it as a method. This prevents show from trying to display + # the whole thing.. + chmod(0755, $pm ); + } + + return 1; } Index: obj/mixin/parser/grammar.inf =================================================================== --- obj/mixin/parser/grammar.inf (revision 23) +++ obj/mixin/parser/grammar.inf (working copy) @@ -1,2 +0,0 @@ -This is a Parse::RecDescent grammar for parsing imperative English -sentences. Index: obj/mixin/parser/disambig.msg =================================================================== --- obj/mixin/parser/disambig.msg (revision 0) +++ obj/mixin/parser/disambig.msg (revision 0) @@ -0,0 +1 @@ +Which one of $choices do you mean? Index: obj/mixin/parser/prepositions.inf =================================================================== --- obj/mixin/parser/prepositions.inf (revision 23) +++ obj/mixin/parser/prepositions.inf (working copy) @@ -1,2 +0,0 @@ -This field needs to have a list of every preposition the parser will -recognize. Index: obj/mixin/parser/parse =================================================================== --- obj/mixin/parser/parse (revision 23) +++ obj/mixin/parser/parse (working copy) @@ -13,6 +13,7 @@ #use Mooix::Thing; #use Mooix::Verb; #use Mooix::Root; +use Data::Dumper; # This is used to mark a Mooix::Thing as coming from a reference. use constant ISREF => Mooix::Thing::_LAST_FIELD + 1; @@ -20,184 +21,307 @@ use constant ISAMB => Mooix::Thing::_LAST_FIELD + 2; # These are referenced by the grammar, and it's up to us to provide them. -use vars qw{$prepositions $pronouns $adjectives $verbs $nouns $answers - $quantifiers}; +use vars qw{$prepositions $relative_tags $pronouns $languages $adjectives + $verbs $nouns $answers $quantifiers}; -# Some global variables used by the subs below (too many, sigh). #{{{ -our @known; # objects the user might be referring to -our @all; # object's we're sure the user knows about, that "all" - # can refer to. -our %nametoobj; # map names to objects. Hash values are arrays. -our %adjtoobj; # map adjectives to the objects that have them -our %pronouns; # map pronouns to objects. Hash values are arrays. -our $loop; # set to 0 to stop the loop from looping -our $session; # set to the session that is being parsed for -our $caller; # set to the avatar that we're acting for -our $stop; # set to 1 to stop processing of the current command -our $parser; # parser object -our $anshandler; # this sub is run if the user seems to aswer a question -our $timings; # set to true to make timing info be output -our $failreason; # why a command couldn't be run (short phrase) -our %incomplete; # if a command can't be run, this holds parts of speech - # that might be missing -our $interceptor; # may be set to a command interceptor verb -our @prompt; # prompt to use for command gathering (optional) -our $dynprompt; # set if prompt is a method -our $debugger; # set the the debugger object, if the user is debugging -#}}} +# This has actually has its values set by the language object, for +# use by the grammar. Basically, it lets the language direct the +# grammar without interference from the "parser". +use vars qw{%lang_to_grammar}; +# Some global variables used by the subs below (too many, sigh). +# objects the user might be referring to +our @known; +# object's we're sure the user knows about, that "all" can refer to. +our @all; +# map names to objects. Hash values are arrays. +our %nametoobj; +# map adjectives to the objects that have them +our %adjtoobj; +# map pronouns to objects. Hash values are arrays. +our %pronouns; +# set to 0 to stop the loop from looping +our $loop; +# set to the session that is being parsed for +our $session; +# set to the avatar that we're acting for +our $caller; +# set to 1 to stop processing of the current command +our $stop; +# parser object +our $parser; +# this sub is run if the user seems to aswer a question +our $anshandler; +# set to true to make timing info be output +our $timings; +# why a command couldn't be run (short phrase) +our $failreason; +# if a command can't be run, this holds parts of speech +our %incomplete; +# that might be missing +# may be set to a command interceptor verb +our $interceptor; +# prompt to use for command gathering (optional) +our @prompt; +# set if prompt is a method +our $dynprompt; +# set the the debugger object, if the user is debugging +our $debugger; +# Ignore all other languages in strings we pull from objects +our $best_lang_code; +# The name of the part of the parse tree that the .cmd files are +# named after. +our $cmd_parse_command; +# A list of the names of the parts of the parse tree that return +# objects and have object-related limits applied to them. +our @cmd_parse_object; +# Command substitutions +our $subst; + +sub strip_xml { + my $lang_code; + $_ = $_[0]; + + # If lang code tags are found + if( m/]*\1>/ ) + { + my $quote_char = $1; + if( m/]*$quote_char>/ ) + { + # If lang code tags of the kind we like are found, use that + $lang_code = $best_lang_code; + } else { + # Else use the first lang code we see + m/]*)$quote_char>/; + $lang_code = $1; + } + # Get rid of all text for non-matching lang tags + # Does not handle nesting, but shouldn't have to + s/]*$quote_char>.*?<\/lang>//g; + + # Get rid of the remaining lang tags. + s/]*$quote_char>//g; + s/<\/lang>//g; + } + + return $_; +} + + # Examine the environment for settings. This is recalled if the parser is # hupped. sub init { #{{{ - # Allow timing info to be output by setting a field in the avatar. - $timings = $caller->benchmarked; - $interceptor = $caller->command_interceptor; - $dynprompt=0; - if ($caller->defines("prompt")) { - if ($caller->implements("prompt")) { - $dynprompt=1; - # prompt is gathered before every command - } - else { - # gather prompt once - @prompt = (prompt => $caller->prompt); - } + # There are no answers, at first. + $answers = genregex(); + + # Let a verb be anything that looks like a word. Starting the + # beginning of a sentence is enough of a disambiguator. + $verbs = qr/\w+/; + + $best_lang_code=$caller->language->code; + $cmd_parse_command = $caller->language->cmd_parse_command; + @cmd_parse_object = $caller->language->cmd_parse_object; + + # Set up a few of the parts of speech that don't change + # dynamically. These are referenced by the grammar. + $prepositions = genregex($caller->language->prepositions); + $relative_tags = genregex($caller->language->relative_tags); + $pronouns = genregex($caller->language->pronouns); + + # Get the language to set up some more complicate stuff for us + my $gv = $caller->language->fieldfile("grammar_variables"); + + if( $gv ) + { + do $gv; + } + + # Use the languages list thus generated. + $languages = genregex( @{$lang_to_grammar{languages}} ); + + # To cut down on startup speed, use the precompiled Grammar.pm, unless + # the grammar file is newer. + my $gram = $caller->language->code . "Grammar"; + my $gpm = $caller->fieldfile( $gram . ".pm" ); + my $gra = $caller->language->fieldfile( "grammar" ); + + if (! $gpm || (stat($gpm))[9] < (stat($gra))[9]) { + $session->write("Compiling grammar, please wait.."); + $caller->parser_compilegrammar( avatar => $caller ); + $gpm = $caller->fieldfile( $gram . ".pm" ); + } + do $gpm; #sorta gross, but what the hey + + # It's "my[code]Grammar" because that's the module name used. See + # compilegrammar. + my $gram2 = "my" . $caller->language->code . "Grammar"; + $parser = $gram2->new; + + # Load in file and compile a sub to do preparsing substitutions. + $subst = eval 'sub { $_=shift;'.$caller->parser_shortcuts."\n".';$_}'; + if ($@) { + $subst = sub {return shift}; # do nothing sub + warn "shortcuts broken: $@"; + } + + # Allow timing info to be output by setting a field in the avatar. + $timings = $caller->benchmarked; + $interceptor = $caller->command_interceptor; + $dynprompt=0; + if ($caller->fieldfile("prompt")) { + if ($caller->implements("prompt")) { + # prompt is gathered before every command + $dynprompt=1; + } else { + # gather prompt once + @prompt = (prompt => $caller->prompt); } - # Turn debugging on or off. - if ($caller->debugging && $caller->defines("debug")) { - $caller->debugger($debugger = $caller->debug); + } + + # Check the caller's language as well. + if ($caller->language->fieldfile("prompt")) { + if ($caller->language->implements("prompt")) { + # prompt is gathered before every command + $dynprompt=2; + } else { + # gather prompt once + @prompt = (prompt => $caller->language->prompt); } - else { - $caller->debugger($debugger = ''); - } + } + + # Turn debugging on or off. + if ($caller->debugging && $caller->defines("debug")) { + $caller->debugger($debugger = $caller->debug); + } + else { + $caller->debugger($debugger = ''); + } } #}}} # Build up and return a list of nearby objects the caller might know about. # The order of this is signifiicant, since the first matching object gets # to run a command. Also sets up the @all list. sub nearbyobjs { #{{{ - my %contentsseen; - my @ret=($caller); - @all=(); + my %contentsseen; + my @ret=($caller); + @all=(); - if ($caller->contents) { - $contentsseen{$caller->index}=1; - push @all, grep ref, $caller->contents->list; - push @ret, @all; + if ($caller->contents) { + $contentsseen{$caller->index}=1; + push @all, grep ref, $caller->contents->list; + push @ret, @all; + } + my $loc=$caller->location; + if ($loc) { + # Add the location near to front. Simple commands like "look" + # are handled by the location often. + $contentsseen{$loc->index}=1; + my @list=grep ref, $loc->contents->list; + push @ret, $loc, @list; + push @all, @list; + + # If the caller's location is itself located somewhere, + # drill down to that uber-location, and add its contents. + # This makes things work properly while you're sitting on + # furniture, etc. + if ($loc->location) { + while ($loc->location) { + $loc=$loc->location; + } + $contentsseen{$loc->index}=1; + @list = grep ref, $loc->contents->list; + push @ret, $loc, @list; + push @all, @list; } - my $loc=$caller->location; - if ($loc) { - # Add the location near to front. Simple commands like "look" - # are handled by the location often. - $contentsseen{$loc->index}=1; - my @list=grep ref, $loc->contents->list; - push @ret, $loc, @list; - push @all, @list; + } - # If the caller's location is itself located somewhere, - # drill down to that uber-location, and add its contents. - # This makes things work properly while you're sitting on - # furniture, etc. - if ($loc->location) { - while ($loc->location) { - $loc=$loc->location; - } - $contentsseen{$loc->index}=1; - @list = grep ref, $loc->contents->list; - push @ret, $loc, @list; - push @all, @list; - } + # Recursively add the contents of every container to the list. + foreach (@ret) { + if (! $contentsseen{$_->index} && $_->contents) { + $contentsseen{$_->index}=1; + # Newly added objects will be processed as part of + # this very same loop. + push @ret, grep ref, $_->accessible_contents; } - - # Recursively add the contents of every container to the list. - foreach (@ret) { - if (! $contentsseen{$_->index} && $_->contents) { - next if $_->closed && ! $_->transparent; - - $contentsseen{$_->index}=1; - # Newly added objects will be processed as part of - # this very same loop. - push @ret, grep ref, $_->contents->list; - } - } - - return @ret; + } + + return @ret; } #}}} # Given a noun, return an object or objects that match it. sub lookup_noun { #{{{ - my $noun = lc(shift); - my $adjectives = shift; - - return unless exists $nametoobj{$noun}; - - my %seen; - my @matches; - my $allplural = 1; - if ($adjectives && @{$adjectives} > 0) { - # Use adjectives to disambiguate. Return all objects that - # match all the adjectives and have the right name. - my %count; - foreach my $adj (@{$adjectives}) { - if (exists $adjtoobj{$adj}) { - map { $count{$_}++ } @{$adjtoobj{$adj}}; - } - } - foreach (@{$nametoobj{$noun}}) { - if (exists $count{$_->[0]} && - $count{$_->[0]} == @{$adjectives} && - ! $seen{$_->[0]->index}) { - $seen{$_->[0]->index}=1; - push @matches, $_->[0]; - $allplural = 0 if $allplural && ! $_->[1]; - } - } + my $noun = lc(shift); + my $adjectives = shift; + + return unless exists $nametoobj{$noun}; + + my %seen; + my @matches; + my $allplural = 1; + if ($adjectives && @{$adjectives} > 0) { + # Use adjectives to disambiguate. Return all objects that + # match all the adjectives and have the right name. + my %count; + foreach my $adj (@{$adjectives}) { + if (exists $adjtoobj{$adj}) { + map { $count{$_}++ } @{$adjtoobj{$adj}}; + } } - else { - foreach (@{$nametoobj{$noun}}) { - if (! $seen{$_->[0]->index}) { - $seen{$_->[0]->index}=1; - push @matches, $_->[0]; - $allplural = 0 if $allplural && ! $_->[1]; - } - } + foreach (@{$nametoobj{$noun}}) { + if (exists $count{$_->[0]} && + $count{$_->[0]} == @{$adjectives} && + ! $seen{$_->[0]->index}) { + $seen{$_->[0]->index}=1; + push @matches, $_->[0]; + $allplural = 0 if $allplural && ! $_->[1]; + } } + } + else { + foreach (@{$nametoobj{$noun}}) { + if (! $seen{$_->[0]->index}) { + $seen{$_->[0]->index}=1; + push @matches, $_->[0]; + $allplural = 0 if $allplural && ! $_->[1]; + } + } + } - if (@matches > 1 && ! $allplural) { - # Mark ambiguities. - my @new; - my @ambs = map { $_->index } @matches; - foreach my $o (@matches) { - # Create a new object pointing at the same real - # object, so this one can be marked ambiguous - # without it polluting other refs to the same - # object. - $_=bless([@{$o}], ref $o); - $_->[ISAMB] = \@ambs; - push @new, $_; - } - return \@new; + if (@matches > 1 && ! $allplural) { + # Mark ambiguities. + my @new; + my @ambs = map { $_->index } @matches; + foreach my $o (@matches) { + # Create a new object pointing at the same real + # object, so this one can be marked ambiguous + # without it polluting other refs to the same + # object. + $_=bless([@{$o}], ref $o); + $_->[ISAMB] = \@ambs; + push @new, $_; } - - return (@matches ? \@matches : undef); + return \@new; + } + + return (@matches ? \@matches : undef); } #}}} # Given a pronoun, return an object or objects that match it. sub lookup_pronoun { #{{{ - my $pronoun = lc(shift); - return $pronouns{$pronoun} if exists $pronouns{$pronoun}; + my $pronoun = lc(shift); + return $pronouns{$pronoun} if exists $pronouns{$pronoun}; - # Non-pre-calculated pronouns. - if ($pronoun =~ /everythings?/ || $pronoun eq 'all') { - my @ret = grep { ! $_->hidden && $_ != $caller } @all; - return \@ret if @ret; - } - elsif ($pronoun eq 'here') { - # There may be no location. - my $loc=$caller->location; - return [$loc] if $loc; - } - return; + # Non-pre-calculated pronouns. + if( grep /^$pronoun$/, $caller->language->all_pronouns ) + { + my @ret = grep { ! $_->hidden && $_ != $caller } @all; + return \@ret if @ret; + } elsif( grep /^$pronoun$/, $caller->language->here_pronouns ) { + # There may be no location. + my $loc=$caller->location; + return [$loc] if $loc; + } + return; } #}}} # Given an object reference (sans the leading "mooix"), return @@ -210,94 +334,109 @@ # if it fails.. But the memoization needs to be undone after each # command that is run, since any command could change the result. sub lookup_reference { #{{{ - my $id = shift; - my $obj = $caller->reference(id => $id); - if ($obj) { - $obj->[ISREF] = 1; - return [$obj]; - } - return; + my $id = shift; + my $obj = $caller->reference(id => $id); + if ($obj) { + $obj->[ISREF] = 1; + return [$obj]; + } + return; } #}}} -# Given a number representation (which might be the raw number, or the -# written-out form, or some ordinal form), return the number it -# represents, or undef if none. -my $word2num_loaded=0; -sub lookup_number { #{{{ - my $word=shift; - if (! $word2num_loaded) { - # Try to use Lingua::EN::Words2Nums, but don't depend - # on it being installed. - eval "use Lingua::EN::Words2Nums"; - if ($@) { - # Install stub function that only does simple numbers. - *::words2nums = sub { - $_ = shift; - return $1 if /^(\d+)(?:st|nd|rd|th)?$/; - return; - }; - } - $word2num_loaded=1; - } - # This is a hack, for "next alias is" type of things. - return 9999 if lc $word eq 'next'; - return words2nums($word); -} #}}} +## # Given a number representation (which might be the raw number, or the +## # written-out form, or some ordinal form), return the number it +## # represents, or undef if none. +## my $word2num_loaded=0; +## sub lookup_number { #{{{ +## my $word=shift; +## if (! $word2num_loaded) { +## # Try to use Lingua::EN::Words2Nums, but don't depend +## # on it being installed. +## eval "use Lingua::EN::Words2Nums"; +## if ($@) { +## # Install stub function that only does simple numbers. +## *::words2nums = sub { +## $_ = shift; +## return $1 if /^(\d+)(?:st|nd|rd|th)?$/; +## return; +## }; +## } +## $word2num_loaded=1; +## } +## # This is a hack, for "next alias is" type of things. +## return 9999 if lc $word eq 'next'; +## return words2nums($word); +## } #}}} +## +## # Called by the grammar to point out recently referred to objects that may +## # set the 'it' pronoun, etc. Pass in a list of objects. +## sub recent_obj { #{{{ +## my @objs = @_; +## if (@objs == 1) { +## # Don't set "it" if the caller talks about themself. +## $pronouns{that} = $pronouns{thats} = $pronouns{it} = +## $pronouns{its} = \@objs +## unless $objs[0] == $caller; +## my $gender=$objs[0]->gender; +## if ($gender) { +## $pronouns{$gender->object_pronoun} = \@objs; +## } +## $pronouns +## } +## elsif (@objs) { +## # TODO To be strictly correct, I should only set 'these' and +## # 'those' if all the objects are not people, and always set +## # 'them'. +## $pronouns{these} = $pronouns{those} = $pronouns{them} = +## $pronouns{their} = \@objs; +## } +## } #}}} -# Called by the grammar to point out recently referred to objects that may -# set the 'it' pronoun, etc. Pass in a list of objects. -sub recent_obj { #{{{ - my @objs = @_; - if (@objs == 1) { - # Don't set "it" if the caller talks about themself. - $pronouns{that} = $pronouns{thats} = $pronouns{it} = - $pronouns{its} = \@objs - unless $objs[0] == $caller; - my $gender=$objs[0]->gender; - if ($gender) { - $pronouns{$gender->object_pronoun} = \@objs; - } - $pronouns - } - elsif (@objs) { - # TODO To be strictly correct, I should only set 'these' and - # 'those' if all the objects are not people, and always set - # 'them'. - $pronouns{these} = $pronouns{those} = $pronouns{them} = - $pronouns{their} = \@objs; - } -} #}}} - # Returns an object if it is inside some other object. # (Actually, it might be called for several objects.) sub is_obj_in_obj { #{{{ - my @objs=@{shift()}; - my $prepositions=shift; - my $container=@{shift()}[0]; - - my @ret; + my @objs=@{shift()}; + my $relative_tag=shift; + my $container=@{shift()}[0]; + ## print STDERR "is_obj relative_tag: $relative_tag \n"; + + my @ret; OBJ: foreach (@objs) { - # If there are prepositions, make sure that the - # prepositions can indeed be used. If so, it'll be in - # the object's preposition list. - if (ref $prepositions) { - my %preps = map { $_ => 1 } $_->preposition; - foreach (@$prepositions) { - next OBJ unless $preps{$_}; - } + # If there is a relative clause tag, make sure that it's + # correct for the relationship the object has to its + # container. + my $ok = 0; + if( length $relative_tag ) { + my $relative_field = $_->relation . "_relatives"; + ## print STDERR "is_obj field: $relative_field\n"; + foreach $_ ($caller->language->$relative_field) + { + ## print STDERR "relative: " . $_ . "\n"; + if( $_ eq $relative_tag ) + { + $ok = 1; + last; + } } - - if ($_->location == $container) { - push @ret, $_; - } + } + + if( ! $ok ) + { + next OBJ; + } + + if ($_->location == $container) { + push @ret, $_; + } } - + # Telling where an object is this way can serve to disambiguate # it, if it was ambiguous. if (@ret == 1) { - $ret[0]->[ISAMB] = undef; + $ret[0]->[ISAMB] = undef; } - + + ## print STDERR "is_obj ret: " . join( ' ', @ret ) . "\n"; return \@ret if @ret; return; } #}}} @@ -307,131 +446,187 @@ # in the prototype, and if the protoype specifies an allowable value set, # the value must be in that set. sub checkproto { #{{{ - my $this = shift; - my $multobj = shift; - my %command = @_; + my $this = shift; + my $multobj = shift; + my %command = @_; - my $cmdfield; - if (exists $command{verb}) { - $cmdfield = lc($command{verb}).".cmd"; + my $cmdfield; + + #print STDERR "command: " . Dumper(\%command) . ".\n"; + + # First try .cmd.[lang], then try .cmd + foreach my $cmd_extension ( ".cmd.".$caller->language->code, ".cmd",) + { + if( exists $command{$cmd_parse_command} ) { + $cmdfield = lc($command{$cmd_parse_command}).$cmd_extension; + } else { + # Check the default.cmd for weirdly formed commands that + # lack a verb. + $cmdfield = "default".$cmd_extension; } - else { - # Check the default.cmd for weirdly formed commands that - # lack a verb. - $cmdfield = "default.cmd"; - } if (! $this) { - use Carp; - Carp::cluck("called on null object"); + use Carp; + Carp::cluck("called on null object"); } - + PROTO: foreach my $prototype ($this->$cmdfield) { - my %remains=%command; + #print STDERR "prototype: $prototype.\n"; + my %remains=%command; - next if $prototype =~ /^#/; - next unless defined $prototype && length $prototype; - my ($prototype, $command) = split(/\s*:\s*/, $prototype, 2); - $command = lc($command{verb}) unless defined $command; - - my (@checknearby, @checktouchable, @checkvisible, + next if $prototype =~ /^#/; + next unless defined $prototype && length $prototype; + my ($prototype, $command) = split(/\s*:\s*/, $prototype, 2); + $command = lc($command{$cmd_parse_command}) unless defined $command; + + my (@checknearby, @checktouchable, @checkvisible, @lockpos, @lockmove, @checkopen); - - my $fail=0; - foreach my $section (split(/\s*,\s*/, $prototype)) { - my ($part, $limits) = $section =~ /(\w+)\s*(?:\((.*)\))?/; - if (! exists $remains{$part}) { - $incomplete{$part}=1; - $fail=1; - next; + + my $fail=0; + foreach my $section (split(/\s*,\s*/, $prototype)) { + my ($orig_part, $limits) = $section =~ /([\w=]+)\s*(?:\((.*)\))?/; + my ( $part, $name ); + #print STDERR "part1: $orig_part.\n"; + # Deal with aliasing + if( $orig_part =~ m/=/ ) + { + $orig_part =~ m/(.*)=(.*)/; + ( $part, $name ) = ( $1, $2 ); + if( $remains{$name} ) + { + $remains{$part} = $remains{$name}; + delete $remains{$name}; + } + } else { + $part = $orig_part; + $name = $orig_part; + } + #print STDERR "part2: $part.\n"; + #print STDERR "remains: " . Dumper(\%remains) . ".\n"; + if (defined $limits) { + #print STDERR "limits: $limits.\n"; + } + if( ! defined $remains{$part} || ! length $remains{$part} ) { + # De-alias parts before setting incomplete, so + # we can do language-specific failure messages. + $incomplete{$name}=1; + $fail=1; + next; + } + if (defined $limits) { + #print STDERR "in main limits.\n"; + my $lockpos=0; + my $checknearby=0; + my $checktouchable=0; + my $checkvisible=0; + my $checkopen=0; + foreach my $limit (split(/\)\(/, $limits)) { + my %limit = map { lc($_) => 1 } split(/\s*\|\s*/, $limit); + my $ok=0; + + if( $limit =~ m/set@/ ) + { + # For example, preposition must be of a + # particular type. So + # io_preposition(set@in_prepositions) + # matches any prepositions in the + # language's in_prepositions field. + my $limit_set = $limit; + $limit_set =~ s/set@([^|)]*)/$1/; + if( grep( /$remains{$part}/, $caller->language->$limit_set ) ) + { + $ok=1; + } } - if (defined $limits) { - my $lockpos=0; - my $checknearby=0; - my $checktouchable=0; - my $checkvisible=0; - my $checkopen=0; - foreach my $limit (split(/\)\(/, $limits)) { - my %limit = map { lc($_) => 1 } split(/\s*\|\s*/, $limit); - my $ok=0; - if ($part eq 'direct_object' || $part eq 'indirect_object') { - # Order is important.. - if ($limit{tomove}) { - push @lockmove, $remains{$part}[0]; - delete $limit{tomove}; # still auto-check nearby - } - if ($limit{nearby} || ! %limit) { - $ok=$lockpos=$checknearby=1; - } - if ($limit{touchable}) { - $ok=$lockpos=$checknearby=$checktouchable=1; - } - if ($limit{visible}) { - $ok=$lockpos=$checknearby=$checkvisible=1; - } - if ($limit{reference} && defined $remains{$part}[0]->[ISREF]) { - $ok=1; - $checknearby=0; - } - if ($limit{single} && (! $multobj || $part ne 'direct_object')) { - $ok=1; - } - if ($limit{anywhere}) { - $ok=1; - $checknearby=0; - } - if ($limit{this} && $remains{$part}[0] == $this) { - $ok=1; - } - if ($limit{open}) { - $ok=$checkopen=1; - } - } - elsif ($part eq 'verb') { - if ($limit{this} && $this == $caller) { - $ok=1; - } - } - elsif (exists $remains{$part} && - defined $remains{$part} && - $limit{$remains{$part}}) { - $ok=1; - } - next PROTO unless $ok; - } - - if ($lockpos) { - push @lockpos, $remains{$part}[0]; - } - if ($checknearby) { - push @checknearby, $remains{$part}[0]; - } - if ($checktouchable) { - push @checktouchable, $remains{$part}[0]; - } - if ($checkvisible) { - push @checkvisible, $remains{$part}[0]; - } - if ($checkopen) { - push @checkopen, $remains{$part}[0]; - } + + ## if( grep( /$part/, @cmd_parse_object ) ) + if ($part eq 'direct_object' || $part eq 'indirect_object') + { + # Order is important.. + if ($limit{tomove}) { + push @lockmove, $remains{$part}[0]; + delete $limit{tomove}; # still auto-check nearby + } + if ($limit{nearby} || ! %limit) { + $ok=$lockpos=$checknearby=1; + } + if ($limit{touchable}) { + $ok=$lockpos=$checknearby=$checktouchable=1; + } + if ($limit{visible}) { + $ok=$lockpos=$checknearby=$checkvisible=1; + } + if ($limit{reference} && defined $remains{$part}[0]->[ISREF]) { + $ok=1; + $checknearby=0; + } + if ($limit{single} && (! $multobj || $part ne 'direct_object')) { + $ok=1; + } + if ($limit{anywhere}) { + $ok=1; + $checknearby=0; + } + if ($limit{this} && $remains{$part}[0] == $this) { + $ok=1; + } + if ($limit{open}) { + $ok=$checkopen=1; + } + } elsif( $part eq $cmd_parse_command ) { + if ($limit{this} && $this == $caller) { + $ok=1; + } + } elsif (exists $remains{$part} && + defined $remains{$part} && + $limit{$remains{$part}}) { + # This section handles stuff + # like do_preposition(down), + # which matches only if the + # literal word "down" is in + # there as a do_preposition. + $ok=1; } + next PROTO unless $ok; + } - delete $remains{$part}; + if ($lockpos) { + push @lockpos, $remains{$part}[0]; + } + if ($checknearby) { + push @checknearby, $remains{$part}[0]; + } + if ($checktouchable) { + push @checktouchable, $remains{$part}[0]; + } + if ($checkvisible) { + push @checkvisible, $remains{$part}[0]; + } + if ($checkopen) { + push @checkopen, $remains{$part}[0]; + } } - delete $remains{do_preposition}; - delete $remains{io_preposition}; - if ($fail) { - if (%remains) { - %incomplete=(); - } - next; + + delete $remains{$part}; + } + + #print STDERR "remains after most stuff: " . Dumper(\%remains) . ".\n"; + + clean_remains( \%command, \%remains ); + + #print STDERR "remains after all stuff: " . Dumper(\%remains) . ".\n"; + if ($fail) { + if (%remains) { + %incomplete=(); } - #print STDERR "$this $cmdfield remains: ".join(", ", keys %remains)."\n"; - return ($command, \@checknearby , \@checktouchable, \@checkvisible, - \@lockpos, \@lockmove, \@checkopen) unless %remains; + next; + } + #print STDERR "$this $cmdfield remains: ".join(", ", keys %remains)."\n"; + return ($command, \@checknearby , \@checktouchable, \@checkvisible, + \@lockpos, \@lockmove, \@checkopen, $prototype) unless %remains; } - return; # failure + } + return; # failure } #}}} # Given a reference to a sentence and a list of objects, constructs a @@ -442,284 +637,362 @@ # objects are pretty much indistinguishable, and a good question cannot be # constructed. sub gen_disambiguator { #{{{ - my %sentence=%{shift @_}; - my @objs=@_; - - # Build up a hash of possible answers to the question. - # It'll be used by the returned subroutine. - my %answers; + my $object_type = shift; + my %sentence=%{shift @_}; + my @objs=@_; - # Support "the former" and "the latter" style responses, and - # "both". - if (@objs == 2) { - $answers{former} = [ $objs[0] ]; - $answers{latter} = [ $objs[1] ]; - $answers{both} = [ @objs ]; # XXX would it be better to use the quantifier sub here? + # Build up a hash of possible answers to the question. + # It'll be used by the returned subroutine. + my %answers; + + # Support "the former" and "the latter" style responses, and + # "both". + if (@objs == 2) { + $answers{former} = [ $objs[0] ]; + $answers{latter} = [ $objs[1] ]; + $answers{both} = [ @objs ]; # XXX would it be better to use the quantifier sub here? + } + + # Let's see if the locations of the objects vary; if so they could + # be used to help disambiguate. The hash values will hold the names + # of the locations. + my %locs; + foreach my $obj (@objs) { + my $loc=$obj->location; + next if ! $loc; + my $id=$loc->id; + if (! exists $locs{$id}) { + my $article = strip_xml( $loc->article ); + $locs{$id} = strip_xml( $article ) ." " if length strip_xml( $article ); + $locs{$id} .= strip_xml( $loc->name ); } - - # Let's see if the locations of the objects vary; if so they could - # be used to help disambiguate. The hash values will hold the names - # of the locations. - my %locs; - foreach my $obj (@objs) { - my $loc=$obj->location; - next if ! $loc; - my $id=$loc->id; - if (! exists $locs{$id}) { - my $article = $loc->article; - $locs{$id} = "$article " if length $article; - $locs{$id} .= $loc->name; - } + } + + # It's quite possible that two objects have nothing really to + # distinguish them. So, this hash will be used to keep track of + # unique choices. + my %seen; + # And this array will hold the arrays of objects that each choice + # corresponds to. + my @choices; + my $count=0; + foreach my $obj (@objs) { + #print STDERR "obj: ".Dumper($obj)."\n"; + my $bit=""; + + if( length strip_xml( $obj->article ) ) + { + # The reason to ignore the object's stated article and use + # the definate article is because it looks weird if it asks + # "Do you mean the red ball or a green ball". + $bit .= $caller->language->definate_article; } - # It's quite possible that two objects have nothing really to - # distinguish them. So, this hash will be used to keep track of - # unique choices. - my %seen; - # And this array will hold the arrays of objects that each choice - # corresponds to. - my @choices; - my $count=0; - foreach my $obj (@objs) { - my $bit=""; - # The reason to ignore the object's stated article and use - # "the" is because it looks weird if it asks "Do you mean - # the red ball or a green ball". - $bit .= "the " if length $obj->article; - my @adj=$obj->adjective; - # Add the adjectives to the answers list. - map { push @{$answers{$_}}, $obj } @adj; - $bit .= join(" ", @adj)." " if @adj; - $bit .= $obj->name; - if (scalar keys %locs > 1) { - my $loc = $obj->location; - if ($loc == $caller) { - $bit .= " you're holding"; - } - else { - my @prep=$obj->preposition; - my $prep=$prep[0]; - $prep = "in" if ! length $prep; - $bit .= " $prep ".$locs{$loc->id}; - } - } + my @adj = map { strip_xml( $_ ) } $obj->adjective; - if (! $seen{$bit}) { - $seen{$bit} = 1; - push @choices, $bit; - push @{$answers{++$count}}, $obj; - } + # Add the adjectives to the answers list. + map { push @{$answers{$_}}, $obj } @adj; + + #print STDERR "bit1: ".Dumper($bit)."\n"; + $bit .= join(" ", @adj)." " if @adj; + #print STDERR "bit2: ".Dumper($bit)."\n"; + $bit .= strip_xml( $obj->name ); + #print STDERR "obj name: ".Dumper($obj->name)."\n"; + #print STDERR "obj name2: ".Dumper( strip_xml( $obj->name ) )."\n"; + #print STDERR "bit3: ".Dumper($bit)."\n"; + if (scalar keys %locs > 1) { + my $loc = $obj->location; + if ($loc == $caller) { + $bit .= $caller->language->holding_postfix; + } + else { + # Generate relative clause lists like "The box which + # is under the table". + my $relation=$obj->relation; + my $relative_field_name = $relation . "_relatives"; + my @relative_field=$caller->language->$relative_field_name; + my $relative=$relative_field[0]; + $bit .= " $relative " . + $locs{$loc->id} . " " . $caller->language->relative_ender; + } } - - # Do all objects seem to be identical? - return undef if @choices == 1; - # Register the answers and the handler. - $answers=genregex(keys %answers); - $anshandler=sub { - my %response = @_; - - my $selected; - if (exists $response{direct_object}) { - # Trim the list down to the objects in @objs. - my %objs = map { $_->index => $_ } @objs; - $selected = [ grep { $objs{$_->index} } @{$response{direct_object}} ]; - } - elsif (exists $response{number} && exists $answers{$response{number}}) { - $selected = $answers{$response{number}}; - } - elsif (exists $response{answer}) { - my $answer = $response{answer}; - # Check each of the user's responses against the - # answers, and select any that match them all. - my $first = shift @$answer; - my @sel = @{$answers{lc $first}}; - foreach my $a (@{$answer}) { - my %matches = map { $_->index => 1} - @{$answers{lc($a)}}; - @sel = grep { $matches{$_->index} } @sel; - } - if (! @sel) { - $session->write("None of the choices is ". - join(" and ", $first, @{$answer})."."); - return 1; - } - $selected = [ @sel ]; - } + #print STDERR "bit: ".Dumper($bit)."\n"; + if (! $seen{$bit}) { + $seen{$bit} = 1; + push @choices, $bit; + push @{$answers{++$count}}, $obj; + } + } - if (! $selected) { - $session->write("Invalid selection."); - return 1; # question was anwered, though not well - } + # Do all objects seem to be identical? + return undef if @choices == 1; - # Register the objects as recently referred to objects now. - recent_obj(@{$selected}); - - # There may be multiple objects still, and this may well lead - # to another round of disambiguation.. anyway, the user has - # answered the question, so deregister it. - $answers=genregex(); - $anshandler=undef; - do_multobj_sentence(%sentence, direct_object => $selected); - return 1; # question was answered, maybe not well - }; - - $choices[-1]="or ".$choices[-1]; - return "Do you mean ".join((@choices > 2) ? ', ' : ' ', @choices)."?"; + #print STDERR "choices1: ".Dumper(\@choices)."\n"; + + # Register the answers and the handler. + $answers=genregex(keys %answers); + print STDERR "answers: ".Dumper(\$answers)."\n"; + $anshandler=sub { + my %response = @_; + print STDERR "In anshandler: ".Dumper(\%response).".\n"; + + my $selected; + if (exists $response{$object_type}) { + # Trim the list down to the objects in @objs. + my %objs = map { $_->index => $_ } @objs; + $selected = [ grep { $objs{$_->index} } @{$response{$object_type}} ]; + } + elsif (exists $response{number} && exists $answers{$response{number}}) { + $selected = $answers{$response{number}}; + } + elsif (exists $response{answer}) { + my $answer = $response{answer}; + # Check each of the user's responses against the + # answers, and select any that match them all. + my $first = shift @$answer; + my @sel = @{$answers{lc $first}}; + foreach my $a (@{$answer}) { + my %matches = map { $_->index => 1} + @{$answers{lc($a)}}; + @sel = grep { $matches{$_->index} } @sel; + } + if (! @sel) { + $session->write("None of the choices is ". + join(" and ", $first, @{$answer})."."); + return 1; + } + $selected = [ @sel ]; + } + + if (! $selected) { + $session->write("Invalid selection."); + return 1; # question was anwered, though not well + } + + # Register the objects as recently referred to objects now. + $lang_to_grammar{recent_obj}->(@{$selected}); + + # There may be multiple objects still, and this may well lead + # to another round of disambiguation.. anyway, the user has + # answered the question, so deregister it. + $answers=genregex(); + $anshandler=undef; + my %new_sentence = %sentence; + delete $new_sentence{$object_type}; + do_multobj_sentence(%new_sentence, $object_type, $selected); + return 1; # question was answered, maybe not well + }; + + my $choices_string = $#choices > 0 + ? join( + $caller->language->list_seperator, + @choices[0 .. $#choices-1] + ) + . $caller->language->list_seperator_last + . $choices[-1] + : $choices[0]; + + #$choices[-1]="or ".$choices[-1]; + + #print STDERR "choices2: ".Dumper(\@choices)."\n"; + + # Hack together the message + my $msg_field = 'parser_disambig.msg'; + my $msg = strip_xml( $caller->$msg_field ); + $msg =~ s/\$choices/$choices_string/; + + return $msg; + #return "Do you mean ".join((@choices > 2) ? ', ' : ' ', @choices)."?"; } #}}} # This takes care of a sentence that has multiple direct objects in it. # Detecting ambiguously referred to direct objects and properly dispatching # everything is a mite complicated. sub do_multobj_sentence { #{{{ - my %sentence = @_; - + my %sentence = @_; + #print STDERR "In do_multiobj: ".Dumper(\%sentence).".\n"; + + foreach my $object_type (@cmd_parse_object) + { + #print STDERR "In do_multiobj foreach: $object_type.\n"; # Putting things in a hash prevents operating on the same direct # object twice. - my %dobjs = map { $_->index => $_ } @{$sentence{direct_object}}; - + my %my_objs = map { $_->index => $_ } @{$sentence{$object_type}}; + + if (scalar values %my_objs <= 1) { + if( @{$sentence{$object_type}} == 0 ) + { + delete $sentence{$object_type}; + } + next; + } + # Check to see if there are any possibly ambiguous references to # objects. my $first_time = 1; - my @list=values %dobjs; - foreach my $direct_object (@list) { - next unless $direct_object->[ISAMB]; - if ($first_time && $direct_object->[ISAMB] && - grep { $_ ne $direct_object->index && $dobjs{$_} } @{$direct_object->[ISAMB]}) { - # Check to see which of the direct objects this - # sentence can actually be run on, and ignore the - # rest. That might elminiate the ambiguities. It is - # a bit expensive though. - $first_time = 0; - foreach my $direct_object (values %dobjs) { - # Test, don't do it. - if (! do_sentence(0, 0, "", %sentence, direct_object => [ $direct_object ])) { - delete $dobjs{$direct_object->index}; - } - } - last if ! %dobjs; # whoops, none can be used. + my @list=values %my_objs; + foreach my $object (@list) { + next unless $object->[ISAMB]; + if ($first_time && $object->[ISAMB] && + grep { $_ ne $object->index && $my_objs{$_} } @{$object->[ISAMB]}) { + # Check to see which of the direct objects this + # sentence can actually be run on, and ignore the + # rest. That might elminiate the ambiguities. It is + # a bit expensive though. + $first_time = 0; + foreach my $object (values %my_objs) { + # Test, don't do it. + if (! do_sentence(0, 0, "", %sentence, $object_type => [ $object ])) { + delete $my_objs{$object->index}; + } } - if ($direct_object && $dobjs{$direct_object->index} && - grep { $_ ne $direct_object->index && $dobjs{$_} } @{$direct_object->[ISAMB]}) { - my @possibles=map { $dobjs{$_} } grep { $dobjs{$_} } @{$direct_object->[ISAMB]}; - my $disambiguator=gen_disambiguator(\%sentence, @possibles); - if (! defined $disambiguator) { - # Act on only one of the objects, since - # they are all much the same. - $session->write("(Picking one of them at random ...)"); - return do_sentence(1, 0, '', %sentence, direct_object => [ $possibles[rand @possibles] ]); - } - else { - $session->write($disambiguator); - # The ISAMB flag needs to be unset now; these - # objects might be used again and it shouldn't - # taint them. - $_->[ISAMB] = undef foreach @list; - return; - } + last if ! %my_objs; # whoops, none can be used. + } + if ($object && $my_objs{$object->index} && + grep { $_ ne $object->index && $my_objs{$_} } @{$object->[ISAMB]}) { + my @possibles=map { $my_objs{$_} } grep { $my_objs{$_} } @{$object->[ISAMB]}; + print STDERR "In do_multiobj disam.\n"; + my $disambiguator=gen_disambiguator( $object_type, \%sentence, @possibles); + if (! defined $disambiguator) { + # Act on only one of the objects, since + # they are all much the same. + $session->write("(Picking one of them at random ...)"); + return do_sentence(1, 0, '', %sentence, $object_type => [ $possibles[rand @possibles] ]); } - $direct_object->[ISAMB] = undef; # not any more + else { + $session->write($disambiguator); + # The ISAMB flag needs to be unset now; these + # objects might be used again and it shouldn't + # taint them. + $_->[ISAMB] = undef foreach @list; + print STDERR "In do_multiobj disam still.\n"; + return; + } + } + $object->[ISAMB] = undef; # not any more } + print STDERR "In do_multiobj done foreach.\n"; - if (! %dobjs) { - showfailure("", %sentence); - return; + if (! %my_objs) { + print STDERR "In do_multiobj showfailure.\n"; + showfailure("", %sentence); + return; } - - if (scalar values %dobjs == 1) { - # There is only one d.o. left after deduping and so on. - if (! do_sentence(1, 0, '', %sentence, direct_object => [ values %dobjs ])) { - showfailure('', %sentence, direct_object => [ values %dobjs ]); - return; - } - return 1; + } + + foreach my $object_type (@cmd_parse_object) + { + my %my_objs = map { $_->index => $_ } @{$sentence{$object_type}}; + + if (scalar values %my_objs < 1) { + if( @{$sentence{$object_type}} == 0 ) + { + delete $sentence{$object_type}; + } + next; } + if (scalar values %my_objs == 1) { + # There is only one d.o. left after deduping and so on. + if (! do_sentence(1, 0, '', %sentence, $object_type => [ values %my_objs ])) { + showfailure('', %sentence, $object_type => [ values %my_objs ]); + return; + } + return 1; + } + # Do the sentence once per direct object. Do it in the original order # the user requested, skipping items that aren't in the hash. Delay # failures until end; if everything failed just show one failure. my @failed; my $tried = 0; - foreach my $direct_object (@{$sentence{direct_object}}) { - next unless $dobjs{$direct_object->index}; - $tried++; - if (! do_sentence(1, 1, $direct_object->name.": ", %sentence, direct_object => [ $direct_object ])) { - push @failed, $direct_object; - } - # Don't operate on this object again.. - delete $dobjs{$direct_object->index}; + foreach my $object (@{$sentence{$object_type}}) { + next unless $my_objs{$object->index}; + $tried++; + if (! do_sentence(1, 1, strip_xml( $object->name ).": ", %sentence, $object_type => [ $object ])) { + push @failed, $object; + } + # Don't operate on this object again.. + delete $my_objs{$object->index}; } if (@failed) { - if (@failed == $tried) { - showfailure("", %sentence, direct_object => $sentence{direct_object}->[0]); - } - else { - showfailure($_->name.": ", %sentence, direct_object => [ $_ ]) - foreach @failed; - } - return; + if (@failed == $tried) { + showfailure("", %sentence, $object_type => $sentence{$object_type}->[0]); + } + else { + showfailure( strip_xml( $_->name ).": ", %sentence, $object_type => [ $_ ]) + foreach @failed; + } + return; } else { - return 1; + return 1; } + } } #}}} - + # Given a sentence finds the object that can handle the command and runs # it. Returns true if something could be done, and false otherwise. sub do_sentence { #{{{ - my $reallydo = shift; # set if the command should really be executed + my $reallydo = shift; # set if the command should really be executed my $multobj = shift; # set if there are really multiple d.o.'s my $prefix = shift; # prefix text to display before output my %sentence = @_; - - my @objs=@known; - - # The caller's command_intercept can, as a special case, intercept - # *anything*. - if (defined $interceptor && length $interceptor) { - my $ret=runcommand($caller, $interceptor, \%sentence); - return $ret if $ret; + + my @objs=@known; + + # The caller's command_intercept can, as a special case, intercept + # *anything*. + if (defined $interceptor && length $interceptor) { + my $ret=runcommand($caller, $interceptor, \%sentence); + return $ret if $ret; + } + + # First, look for verbs on the direct or indirect object. Doing + # this first optimizes for the common case. It also means that is + # the direct or indirect object was referred to using mooix:, and + # is not nearby, they still can have verbs run on them. + if ($sentence{direct_object} && @{$sentence{direct_object}}) { + if (dispatch($reallydo, $multobj, $sentence{direct_object}->[0], $prefix, %sentence)) { + return 1; } - - # First, look for verbs on the direct or indirect object. Doing - # this first optimizes for the common case. It also means that is - # the direct or indirect object was referred to using mooix:, and - # is not nearby, they still can have verbs run on them. - if ($sentence{direct_object} && @{$sentence{direct_object}}) { - if (dispatch($reallydo, $multobj, $sentence{direct_object}->[0], $prefix, %sentence)) { - return 1; - } - @objs=grep { $_ != $sentence{direct_object} } @objs; + @objs=grep { $_ != $sentence{direct_object} } @objs; + } + elsif ($sentence{indirect_object} && @{$sentence{indirect_object}}) { + if (dispatch($reallydo, $multobj, $sentence{indirect_object}->[0], $prefix, %sentence)) { + return 1; } - elsif ($sentence{indirect_object} && @{$sentence{indirect_object}}) { - if (dispatch($reallydo, $multobj, $sentence{indirect_object}->[0], $prefix, %sentence)) { - return 1; - } - @objs=grep { $_ != $sentence{indirect_object} } @objs; + @objs=grep { $_ != $sentence{indirect_object} } @objs; + } + + # Failing all the above, just try checking all other nearby objects. + foreach my $obj (@objs) { + if (dispatch($reallydo, $multobj, $obj, $prefix, %sentence)) { + return 1; } - - # Failing all the above, just try checking all other nearby objects. - foreach my $obj (@objs) { - if (dispatch($reallydo, $multobj, $obj, $prefix, %sentence)) { - return 1; - } + } + + # If we have only a verb and a preposition, then it could be that + # instead of a preposition, they meant to refer to an object. For + # example, "go down" causes down to be parsed as a preposition. + if (! grep { $_ ne 'verb' && $_ ne 'preposition' } keys %sentence) { + my $direct_object = lookup_noun( + $caller->dexml( + text => $sentence{preposition}, + language => $best_lang_code + ) + ); + if ($direct_object) { + return do_sentence($reallydo, 0, $prefix, + verb => $sentence{verb}, + direct_object => $direct_object); } + } - # If we have only a verb and a preposition, then it could be that - # instead of a preposition, they meant to refer to an object. For - # example, "go down" causes down to be parsed as a preposition. - if (! grep { $_ ne 'verb' && $_ ne 'preposition' } keys %sentence) { - my $direct_object = lookup_noun($sentence{preposition}); - if ($direct_object) { - return do_sentence($reallydo, 0, $prefix, - verb => $sentence{verb}, - direct_object => $direct_object); - } - } - - return; # failure + return; # failure } #}}} - + # This is called when the user's command cannot be run for some reason. If # $failreason is set, then it is just displayed, telling them why whatever # they wanted to do can't work. If it is empty, then if %incomplete has @@ -729,418 +1002,472 @@ # The first parameter is an optional prefix to prepend to the output. # The sentence is required. sub showfailure { #{{{ - my $prefix = shift; - my %sentence = @_; - $prefix = "" unless defined $prefix; - # These parts of speech almost never matter. - delete $incomplete{io_preposition}; - delete $incomplete{do_preposition}; - - if (length $failreason) { - $session->write($prefix.$failreason); - } - elsif (%incomplete) { - # Build up a question indicating what parts of speech they - # were missing. - my @message; - if (! $sentence{verb}) { - # Whee, they typed something really weird. - $session->write("Beg pardon?"); - return; - } - elsif ($incomplete{direct_object}) { - push @message, $sentence{verb}, "what"; - if ($incomplete{indirect_object}) { - push @message, "where"; - } + my $prefix = shift; + my %sentence = @_; + $prefix = "" unless defined $prefix; - # Set up answer handler. - $anshandler=sub { - my %response = @_; - if (exists $response{direct_object}) { - $sentence{direct_object} = $response{direct_object}; - recent_obj(@{$response{direct_object}}); - $answers=genregex(); - $anshandler=undef; - do_multobj_sentence(%sentence); - return 1; + if (length $failreason) { + $session->write($prefix.$failreason); + } elsif (%incomplete) { + # Build up a question indicating what parts of speech they + # were missing. + my @message; + if (! $sentence{$cmd_parse_command}) { + # Whee, they typed something really weird. + my $msg_field = 'parser_wtf.msg'; + $session->write( + strip_xml( $caller->$msg_field ) + ); + return; + } elsif( grep { exists $incomplete{$_} } @cmd_parse_object ) { + foreach my $part (@cmd_parse_object) + { + if ($incomplete{$part}) { + push @message, $sentence{$cmd_parse_command}; + + my $question_word = 'question_word_'.$part; + push @message, $caller->language->$question_word; + + # Set up answer handler. + $anshandler=sub { + my %response = @_; + if( exists $response{$part} ) { + + # This part allows us to grab extra + # fields associated with the object, + # like the associated preposition. + my $extra_parts_field = $part.'_extras'; + foreach my $extra_part ($caller->language->$extra_parts_field) + { + if( exists $response{$extra_part} ) { + $sentence{$extra_part} = $response{$extra_part}; } - return; - }; - } - elsif ($incomplete{indirect_object}) { - push @message, $sentence{verb}; - push @message, "it"; - push @message, "where"; - - # Set up answer handler. - $anshandler=sub { - my %response = @_; - if (exists $response{direct_object}) { - $sentence{indirect_object} = $response{direct_object}; - recent_obj(@{$response{direct_object}}); - $sentence{io_preposition} = $response{do_preposition} - if exists $response{do_preposition}; - $answers=genregex(); - $anshandler=undef; - do_multobj_sentence(%sentence); - return 1; - } - return; - }; - } - else { - $session->write("You need to supply ". - join(" and ", map { s/_/ /g; "a $_" } - keys %incomplete)."."); + } + $sentence{$part} = $response{$part}; + $lang_to_grammar{recent_obj}->(@{$response{$part}}); + $answers=genregex(); + $anshandler=undef; + do_multobj_sentence(%sentence); + return 1; + } return; + }; + + last; } - $session->write(ucfirst join(" ", @message)."?"); + + } + } else { + my @incompletes = map { clean_incomplete( $_ ) } keys %incomplete; + + my $incompletes_string = $#incompletes > 0 + ? $caller->language->indefinate_article . + join( + $caller->language->list_seperator . + $caller->language->indefinate_article , + @incompletes[0 .. $#incompletes-1] + ) + . $caller->language->list_seperator_last + . $caller->language->indefinate_article . $incompletes[-1] + : $caller->language->indefinate_article . $incompletes[0]; + + # Hack together the message + my $msg_field = 'parser_supply.msg'; + my $msg = strip_xml( $caller->$msg_field ); + $msg =~ s/\$incompletes/$incompletes_string/; + $session->write($msg); + + return; } - else { - $session->write($prefix."You can't do that."); + + my $message = join(" ", @message); + $message = $caller->language->question_starter . $message . $caller->language->question_ender; + + if( $caller->language->upper_case_initial ) + { + $message = ucfirst $message; } + + $session->write( $message ); + } else { + # Hack together the message + my $msg_field = 'parser_confused.msg'; + my $msg = strip_xml( $caller->$msg_field ); + $session->write($msg); + } } #}}} # Tries to find a prototype in an object to match a command, and if it # finds one, does necessary locking, runs the command and returns true. sub dispatch { #{{{ - my $reallydo = shift; # really lock and run command + my $reallydo = shift; # really lock and run command my $multobj = shift; # set if there are really multiple d.o's my $this = shift; # object to check my $prefix = shift; # prefix text to display before output my %sentence = @_; # the parameters of the command my ($command, $checknearby, $checktouchable, $checkvisible, - $lockpos, $lockmove, $checkopen) = - checkproto($this, $multobj, %sentence); - return 0 unless defined $command; - - # Now we have to lock some objects in position, and maybe - # check to make sure they're still nearby (to avoid races). - # - # Keeps locks open until the function returns, and keeps - # track of what is locked. - my %locked; + $lockpos, $lockmove, $checkopen, $prototype) = + checkproto($this, $multobj, %sentence); + return 0 unless defined $command; + #print STDERR "dispactch: $command, $checknearby, $checktouchable, $checkvisible, $lockpos, $lockmove, $checkopen.\n"; - if ($reallydo) { - # First, handle any objects that need to be locked for move. - # This is an exclusive lock. - foreach my $obj (@{$lockmove}) { - next if $locked{$obj->index}; - return unless $locked{$obj->index} = - $obj->getlock(LOCK_EX); - } + # Now we have to lock some objects in position, and maybe + # check to make sure they're still nearby (to avoid races). + # + # Keeps locks open until the function returns, and keeps + # track of what is locked. + my %locked; - # Then, lock any remaining objects that need to be locked, - # to prevent moving by third parties. This is a shared lock. - foreach my $obj (@{$lockpos}) { - next if $locked{$obj->index}; - return unless $locked{$obj->index} = - $obj->getlock(LOCK_SH); - } + if ($reallydo) { + # First, handle any objects that need to be locked for move. + # This is an exclusive lock. + foreach my $obj (@{$lockmove}) { + next if $locked{$obj->index}; + return unless $locked{$obj->index} = + $obj->getlock(LOCK_EX); } - - # The caller's location. - my $cloc=$caller->location; - # If the caller's location is itself in some location, use its - # location. - while ($cloc && $cloc->location) { - $cloc = $cloc->location; + + # Then, lock any remaining objects that need to be locked, + # to prevent moving by third parties. This is a shared lock. + foreach my $obj (@{$lockpos}) { + next if $locked{$obj->index}; + return unless $locked{$obj->index} = + $obj->getlock(LOCK_SH); } - - # Check to see if objects that must be touchable are. That means - # that every container between the user and the object must be - # open. - foreach my $obj (@{$checktouchable}) { - my $loc=$obj->location; - if (! $loc) { - next if $cloc == $obj; - return; - } - while ($loc && ($loc != $caller && $cloc != $loc && $cloc != $obj)) { - # The container the object is in needs to be locked - # in position to prevent it from being moved in - # the middle of a command. - if ($locked{$obj->index}) { - if (! $locked{$loc->index}) { - $locked{$loc->index} = $loc->getlock(LOCK_SH); - } - } - # And the container must be locked open, to prevent - # it from closing during the command. - if ($loc && ! $locked{"closed".$loc->index}) { - $locked{"closed".$loc->index} = $loc->getlock(LOCK_SH, "closed"); - } - # Only check its state after taking the lock. - if (! $loc || $loc->closed) { - $failreason="You can't touch that."; - return; - } - $loc=$loc->location; # advance to next container - } - return unless $loc; + } + + # The caller's location. + my $cloc=$caller->location; + # If the caller's location is itself in some location, use its + # location. + while ($cloc && $cloc->location) { + $cloc = $cloc->location; + } + + # Check to see if objects that must be touchable are. That means + # that every container between the user and the object must be + # open, or the objects must be otherwise accessible. + foreach my $obj (@{$checktouchable}) { + my $loc=$obj->location; + if (! $loc) { + next if $cloc == $obj; + return; } - - # Check to see if objects that must be nearby are. - foreach my $obj (@{$checknearby}) { - my $loc=$obj->location; - if (! $loc) { - next if $cloc == $obj; - return; + while ($loc && ($loc != $caller && $cloc != $loc && $cloc != $obj)) { + # The container the object is in needs to be locked + # in position to prevent it from being moved in + # the middle of a command. + if ($locked{$obj->index}) { + if (! $locked{$loc->index}) { + $locked{$loc->index} = $loc->getlock(LOCK_SH); } - while ($loc && ($loc != $caller && $cloc != $loc && $cloc != $obj)) { - # The container the object is in needs to be locked - # in position to prevent it from being mov