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.