Subject: Re: How to eval a function from a string with its name? From: Erik Naggum <erik@naggum.net> Date: Wed, 20 Jun 2001 23:31:06 GMT Newsgroups: comp.lang.lisp Message-ID: <3202068662626900@naggum.net> * Juan Pablo Hierro Álvarez > Is there any way to evaluate the function when only *name* is known? Three steps in principle: From name to symbol, from symbol to function, evaluating function. When you type in (name) to the Lisp read-eval-print loop, you get the whole package. However, if you want to do it yourself, it is more work. Because Common Lisp symbols are case sensitive, but the Common Lisp reader by default is not, the first snag is to get from the most common way to write symbol names these days to the "internal" name of the same symbol in Common Lisp. (Various efforts to fix this problem have ran into political problems. The problem is not so much that there is a specific case internally as the fact that we have no standard tools to convert between string and symbol except by going through the reader and printer, both very, very expensive facilities.) Here's a shot at the conversion between string (name) and symbol, more elaborate than efficient to show the concepts involved more clearly: (defun string-cases-with-mask (string quoted-character-mask) "Returns the cases used in the string. The return value is either nil, for no cases, :lower for all lower-case, :upper for all upper-case, or :both for the existence of both lower- and upper-case letters. Only characters that satisfy both-case-p are tested. The quoted-character-mask must be a vector of boolean values or bits, and causes non-nil/non-zero entries to exclude characters from consideration at the corresponding position in the string." (let ((lower nil) (upper nil)) (map nil (lambda (char mask) (when (and (or (not mask) (zerop mask)) (both-case-p char)) (if (lower-case-p char) (setq lower t) (setq upper t)))) string quoted-character-mask) (cond ((and lower upper) :both) (lower :lower) (upper :upper) (t nil)))) (defun string-upcase-with-mask (string quoted-character-mask) "Returns a new string which has all unmasked characters upcased that satisfy both-case-p, as some lower-case characters may not have a corresponding upper-case version. The quoted-cahracter-mask must be a vector of boolean values or bits, and causes non-nil/non-zero entries to exclude characters from consideration at the corresponding position in the string." (map 'string (lambda (char mask) (if (and (or (not mask) (zerop mask)) (both-case-p char)) (char-upcase char) char)) string quoted-character-mask)) (defun string-downcase-with-mask (string quoted-character-mask) "Returns a new string which has all unmasked characters downcased that satisfy both-case-p, as some upper-case characters may not have a corresponding lower-case version. The quoted-character-mask must be a vector of boolean values or bits, and causes non-nil/non-zero entries to exclude characters from consideration at the corresponding position in the string." (map 'string (lambda (char mask) (if (and (or (not mask) (zerop mask)) (both-case-p char)) (char-downcase char) char)) string quoted-character-mask)) (defun string-symbol (string &key (package *package*) (readtable *readtable*) (quoted-character-mask nil) (if-does-not-exist nil)) "Returns the symbol named by string as if read by the Lisp reader. Obeys the readtable-case of the specified (or current) readtable. If the symbol is found in the specified (or current) package, it is returned like find-symbol does. If the symbol does not exist, if-does-not-exist controls the behavior and return values: nil, the default, returns nil and nil as find-symbol does. :intern interns the symbol in the specified (or current) package and returns the symbol and nil. :error signals an error of type simple-error. The quoted-character-mask, if supplied, must be a vector of boolean values \(nil/t or 0/1 are accepted) , and causes non-nil/non-zero entries to exclude characters from consideration at the corresponding position in the string." (unless quoted-character-mask (setq quoted-character-mask (make-array (length string) :element-type bit :initial-element 0))) ;; Do error handling early, instead of after all the work. (ecase if-does-not-exist ((:intern error nil))) (let ((internal-string (ecase (readtable-case readtable) (:upcase (string-upcase-with-mask string quoted-character-mask)) (:downcase (string-downcase-with-mask string quoted-character-mask)) (:preserve string) (:invert (case (string-cases-with-mask string quoted-character-mask) ((:both nil) string) (:upper (string-downcase-with-mask string quoted-character-mask)) (:ower (string-upcase-with-mask string quoted-character-mask))))))) (multiple-value-bind (symbol status) (find-symbol internal-string package) (if status (values symbol status) (case if-does-not-exist (:intern (intern internal-string package)) (:error (error "No symbol with name ~s found in ~s." internal-string (package-name package))) ((nil) (values nil nil))))))) Now, since funcall and apply both accept symbols (which they interpret to mean the global function binding), you can simply call the symbol with the arguments you have already evaluated according to whatever rules your language defines for argument evaluation. > I am thinking of a shell script with the name of the file to be loaded as > a first argument (no problems with a string) and the name of the function > to be evaluated as a second argument (it is passed as a string, too!). Shell scripts that invoke functions (commands and other shell scripts) are actually using the file system as their package implementation, and they have a very, very inefficient way of searching through it, even though some shells have disvoered hash tables and manage to avoid the lookup in some cases. If you must, you can think of the search for the symbol in Common Lisp as if searching a case-insensitive file system for a command you have invoked in a shell script, although this does more to tell you how incredibly silly file systems and shell languages are done compared to real symbol tables (in any language, actually, but usually they live only inside compilers). #:Erik -- Travel is a meat thing.